summaryrefslogtreecommitdiffstats
path: root/accessible
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /accessible
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'accessible')
-rw-r--r--accessible/.eslintrc.js17
-rw-r--r--accessible/aom/AccessibleNode.cpp68
-rw-r--r--accessible/aom/AccessibleNode.h53
-rw-r--r--accessible/aom/moz.build40
-rw-r--r--accessible/atk/ARIAGridAccessibleWrap.h21
-rw-r--r--accessible/atk/AccessibleWrap.cpp1759
-rw-r--r--accessible/atk/AccessibleWrap.h92
-rw-r--r--accessible/atk/ApplicationAccessibleWrap.cpp167
-rw-r--r--accessible/atk/ApplicationAccessibleWrap.h35
-rw-r--r--accessible/atk/AtkSocketAccessible.cpp126
-rw-r--r--accessible/atk/AtkSocketAccessible.h56
-rw-r--r--accessible/atk/DocAccessibleWrap.cpp25
-rw-r--r--accessible/atk/DocAccessibleWrap.h31
-rw-r--r--accessible/atk/HTMLTableAccessibleWrap.h23
-rw-r--r--accessible/atk/HyperTextAccessibleWrap.h21
-rw-r--r--accessible/atk/ImageAccessibleWrap.h21
-rw-r--r--accessible/atk/InterfaceInitFuncs.h44
-rw-r--r--accessible/atk/Platform.cpp377
-rw-r--r--accessible/atk/RootAccessibleWrap.cpp24
-rw-r--r--accessible/atk/RootAccessibleWrap.h34
-rw-r--r--accessible/atk/TextLeafAccessibleWrap.h20
-rw-r--r--accessible/atk/UtilInterface.cpp411
-rw-r--r--accessible/atk/XULListboxAccessibleWrap.h21
-rw-r--r--accessible/atk/XULMenuAccessibleWrap.h20
-rw-r--r--accessible/atk/XULTreeGridAccessibleWrap.h22
-rw-r--r--accessible/atk/moz.build63
-rw-r--r--accessible/atk/nsMai.h149
-rw-r--r--accessible/atk/nsMaiHyperlink.cpp262
-rw-r--r--accessible/atk/nsMaiHyperlink.h55
-rw-r--r--accessible/atk/nsMaiInterfaceAction.cpp105
-rw-r--r--accessible/atk/nsMaiInterfaceComponent.cpp151
-rw-r--r--accessible/atk/nsMaiInterfaceDocument.cpp151
-rw-r--r--accessible/atk/nsMaiInterfaceEditableText.cpp134
-rw-r--r--accessible/atk/nsMaiInterfaceHyperlinkImpl.cpp37
-rw-r--r--accessible/atk/nsMaiInterfaceHypertext.cpp94
-rw-r--r--accessible/atk/nsMaiInterfaceImage.cpp77
-rw-r--r--accessible/atk/nsMaiInterfaceSelection.cpp152
-rw-r--r--accessible/atk/nsMaiInterfaceTable.cpp391
-rw-r--r--accessible/atk/nsMaiInterfaceTableCell.cpp216
-rw-r--r--accessible/atk/nsMaiInterfaceText.cpp629
-rw-r--r--accessible/atk/nsMaiInterfaceValue.cpp133
-rw-r--r--accessible/atk/nsStateMap.h117
-rw-r--r--accessible/base/ARIAMap.cpp1008
-rw-r--r--accessible/base/ARIAMap.h305
-rw-r--r--accessible/base/ARIAStateMap.cpp376
-rw-r--r--accessible/base/ARIAStateMap.h67
-rw-r--r--accessible/base/AccEvent.cpp271
-rw-r--r--accessible/base/AccEvent.h570
-rw-r--r--accessible/base/AccGroupInfo.cpp227
-rw-r--r--accessible/base/AccGroupInfo.h114
-rw-r--r--accessible/base/AccIterator.cpp414
-rw-r--r--accessible/base/AccIterator.h325
-rw-r--r--accessible/base/AccTypes.h94
-rw-r--r--accessible/base/AccessibleOrProxy.cpp27
-rw-r--r--accessible/base/AccessibleOrProxy.h123
-rw-r--r--accessible/base/Asserts.cpp26
-rw-r--r--accessible/base/DocManager.cpp594
-rw-r--r--accessible/base/DocManager.h189
-rw-r--r--accessible/base/EmbeddedObjCollector.cpp81
-rw-r--r--accessible/base/EmbeddedObjCollector.h69
-rw-r--r--accessible/base/EventQueue.cpp344
-rw-r--r--accessible/base/EventQueue.h77
-rw-r--r--accessible/base/EventTree.cpp618
-rw-r--r--accessible/base/EventTree.h121
-rw-r--r--accessible/base/Filters.cpp51
-rw-r--r--accessible/base/Filters.h50
-rw-r--r--accessible/base/FocusManager.cpp404
-rw-r--r--accessible/base/FocusManager.h132
-rw-r--r--accessible/base/Logging.cpp1039
-rw-r--r--accessible/base/Logging.h225
-rw-r--r--accessible/base/MarkupMap.h340
-rw-r--r--accessible/base/NotificationController.cpp947
-rw-r--r--accessible/base/NotificationController.h439
-rw-r--r--accessible/base/Platform.h88
-rw-r--r--accessible/base/Relation.h108
-rw-r--r--accessible/base/RelationType.h163
-rw-r--r--accessible/base/RelationTypeMap.h154
-rw-r--r--accessible/base/Role.h995
-rw-r--r--accessible/base/RoleMap.h1370
-rw-r--r--accessible/base/SelectionManager.cpp232
-rw-r--r--accessible/base/SelectionManager.h133
-rw-r--r--accessible/base/States.h285
-rw-r--r--accessible/base/Statistics.h39
-rw-r--r--accessible/base/StyleInfo.cpp122
-rw-r--r--accessible/base/StyleInfo.h48
-rw-r--r--accessible/base/TextAttrs.cpp913
-rw-r--r--accessible/base/TextAttrs.h478
-rw-r--r--accessible/base/TextRange-inl.h29
-rw-r--r--accessible/base/TextRange.cpp380
-rw-r--r--accessible/base/TextRange.h270
-rw-r--r--accessible/base/TextUpdater.cpp202
-rw-r--r--accessible/base/TextUpdater.h96
-rw-r--r--accessible/base/TreeWalker.cpp325
-rw-r--r--accessible/base/TreeWalker.h144
-rw-r--r--accessible/base/moz.build114
-rw-r--r--accessible/base/nsAccCache.h28
-rw-r--r--accessible/base/nsAccUtils.cpp447
-rw-r--r--accessible/base/nsAccUtils.h243
-rw-r--r--accessible/base/nsAccessibilityService.cpp1885
-rw-r--r--accessible/base/nsAccessibilityService.h442
-rw-r--r--accessible/base/nsAccessiblePivot.cpp924
-rw-r--r--accessible/base/nsAccessiblePivot.h144
-rw-r--r--accessible/base/nsCoreUtils.cpp683
-rw-r--r--accessible/base/nsCoreUtils.h329
-rw-r--r--accessible/base/nsEventShell.cpp79
-rw-r--r--accessible/base/nsEventShell.h67
-rw-r--r--accessible/base/nsTextEquivUtils.cpp364
-rw-r--r--accessible/base/nsTextEquivUtils.h162
-rw-r--r--accessible/generic/ARIAGridAccessible-inl.h39
-rw-r--r--accessible/generic/ARIAGridAccessible.cpp702
-rw-r--r--accessible/generic/ARIAGridAccessible.h138
-rw-r--r--accessible/generic/Accessible-inl.h135
-rw-r--r--accessible/generic/Accessible.cpp2856
-rw-r--r--accessible/generic/Accessible.h1269
-rw-r--r--accessible/generic/ApplicationAccessible.cpp201
-rw-r--r--accessible/generic/ApplicationAccessible.h120
-rw-r--r--accessible/generic/BaseAccessibles.cpp264
-rw-r--r--accessible/generic/BaseAccessibles.h132
-rw-r--r--accessible/generic/DocAccessible-inl.h189
-rw-r--r--accessible/generic/DocAccessible.cpp2387
-rw-r--r--accessible/generic/DocAccessible.h718
-rw-r--r--accessible/generic/FormControlAccessible.cpp193
-rw-r--r--accessible/generic/FormControlAccessible.h76
-rw-r--r--accessible/generic/HyperTextAccessible-inl.h180
-rw-r--r--accessible/generic/HyperTextAccessible.cpp2230
-rw-r--r--accessible/generic/HyperTextAccessible.h592
-rw-r--r--accessible/generic/ImageAccessible.cpp224
-rw-r--r--accessible/generic/ImageAccessible.h87
-rw-r--r--accessible/generic/OuterDocAccessible.cpp218
-rw-r--r--accessible/generic/OuterDocAccessible.h61
-rw-r--r--accessible/generic/RootAccessible.cpp732
-rw-r--r--accessible/generic/RootAccessible.h92
-rw-r--r--accessible/generic/TableAccessible.h187
-rw-r--r--accessible/generic/TableCellAccessible.cpp67
-rw-r--r--accessible/generic/TableCellAccessible.h70
-rw-r--r--accessible/generic/TextLeafAccessible.cpp54
-rw-r--r--accessible/generic/TextLeafAccessible.h51
-rw-r--r--accessible/generic/moz.build70
-rw-r--r--accessible/html/HTMLCanvasAccessible.cpp24
-rw-r--r--accessible/html/HTMLCanvasAccessible.h35
-rw-r--r--accessible/html/HTMLElementAccessibles.cpp204
-rw-r--r--accessible/html/HTMLElementAccessibles.h120
-rw-r--r--accessible/html/HTMLFormControlAccessible.cpp841
-rw-r--r--accessible/html/HTMLFormControlAccessible.h284
-rw-r--r--accessible/html/HTMLImageMapAccessible.cpp228
-rw-r--r--accessible/html/HTMLImageMapAccessible.h89
-rw-r--r--accessible/html/HTMLLinkAccessible.cpp147
-rw-r--r--accessible/html/HTMLLinkAccessible.h51
-rw-r--r--accessible/html/HTMLListAccessible.cpp199
-rw-r--r--accessible/html/HTMLListAccessible.h105
-rw-r--r--accessible/html/HTMLSelectAccessible.cpp610
-rw-r--r--accessible/html/HTMLSelectAccessible.h222
-rw-r--r--accessible/html/HTMLTableAccessible.cpp1139
-rw-r--r--accessible/html/HTMLTableAccessible.h226
-rw-r--r--accessible/html/moz.build50
-rw-r--r--accessible/interfaces/ia2/IA2Marshal.def11
-rw-r--r--accessible/interfaces/ia2/IA2Marshal.dll.manifest12
-rw-r--r--accessible/interfaces/ia2/IA2Marshal.rc5
-rw-r--r--accessible/interfaces/ia2/IA2Typelib.idl61
-rw-r--r--accessible/interfaces/ia2/Makefile.in108
-rw-r--r--accessible/interfaces/ia2/moz.build33
-rw-r--r--accessible/interfaces/moz.build40
-rw-r--r--accessible/interfaces/msaa/AccessibleMarshal.def11
-rw-r--r--accessible/interfaces/msaa/AccessibleMarshal.rc5
-rw-r--r--accessible/interfaces/msaa/ISimpleDOMDocument.idl83
-rw-r--r--accessible/interfaces/msaa/ISimpleDOMNode.idl188
-rw-r--r--accessible/interfaces/msaa/ISimpleDOMText.idl79
-rw-r--r--accessible/interfaces/msaa/Makefile.in50
-rw-r--r--accessible/interfaces/msaa/moz.build41
-rw-r--r--accessible/interfaces/nsIAccessibilityService.idl108
-rw-r--r--accessible/interfaces/nsIAccessible.idl300
-rw-r--r--accessible/interfaces/nsIAccessibleApplication.idl34
-rw-r--r--accessible/interfaces/nsIAccessibleCaretMoveEvent.idl18
-rw-r--r--accessible/interfaces/nsIAccessibleDocument.idl74
-rw-r--r--accessible/interfaces/nsIAccessibleEditableText.idl56
-rw-r--r--accessible/interfaces/nsIAccessibleEvent.idl455
-rw-r--r--accessible/interfaces/nsIAccessibleHideEvent.idl28
-rw-r--r--accessible/interfaces/nsIAccessibleHyperLink.idl86
-rw-r--r--accessible/interfaces/nsIAccessibleHyperText.idl54
-rw-r--r--accessible/interfaces/nsIAccessibleImage.idl31
-rw-r--r--accessible/interfaces/nsIAccessibleObjectAttributeChangedEvent.idl21
-rw-r--r--accessible/interfaces/nsIAccessiblePivot.idl259
-rw-r--r--accessible/interfaces/nsIAccessibleRelation.idl173
-rw-r--r--accessible/interfaces/nsIAccessibleRole.idl980
-rw-r--r--accessible/interfaces/nsIAccessibleSelectable.idl59
-rw-r--r--accessible/interfaces/nsIAccessibleStateChangeEvent.idl29
-rw-r--r--accessible/interfaces/nsIAccessibleStates.idl76
-rw-r--r--accessible/interfaces/nsIAccessibleTable.idl268
-rw-r--r--accessible/interfaces/nsIAccessibleTableChangeEvent.idl20
-rw-r--r--accessible/interfaces/nsIAccessibleText.idl237
-rw-r--r--accessible/interfaces/nsIAccessibleTextChangeEvent.idl33
-rw-r--r--accessible/interfaces/nsIAccessibleTextRange.idl159
-rw-r--r--accessible/interfaces/nsIAccessibleTypes.idl81
-rw-r--r--accessible/interfaces/nsIAccessibleValue.idl35
-rw-r--r--accessible/interfaces/nsIAccessibleVirtualCursorChangeEvent.idl34
-rw-r--r--accessible/interfaces/nsIXBLAccessible.idl20
-rw-r--r--accessible/ipc/DocAccessibleChildBase.cpp97
-rw-r--r--accessible/ipc/DocAccessibleChildBase.h81
-rw-r--r--accessible/ipc/DocAccessibleParent.cpp526
-rw-r--r--accessible/ipc/DocAccessibleParent.h198
-rw-r--r--accessible/ipc/IPCTypes.h49
-rw-r--r--accessible/ipc/ProxyAccessibleBase.cpp175
-rw-r--r--accessible/ipc/ProxyAccessibleBase.h211
-rw-r--r--accessible/ipc/ProxyAccessibleShared.h277
-rw-r--r--accessible/ipc/moz.build61
-rw-r--r--accessible/ipc/other/DocAccessibleChild.cpp1998
-rw-r--r--accessible/ipc/other/DocAccessibleChild.h489
-rw-r--r--accessible/ipc/other/PDocAccessible.ipdl264
-rw-r--r--accessible/ipc/other/ProxyAccessible.cpp1080
-rw-r--r--accessible/ipc/other/ProxyAccessible.h50
-rw-r--r--accessible/ipc/other/moz.build47
-rw-r--r--accessible/ipc/win/COMPtrTypes.cpp50
-rw-r--r--accessible/ipc/win/COMPtrTypes.h27
-rw-r--r--accessible/ipc/win/DocAccessibleChild.cpp237
-rw-r--r--accessible/ipc/win/DocAccessibleChild.h318
-rw-r--r--accessible/ipc/win/PDocAccessible.ipdl75
-rw-r--r--accessible/ipc/win/PlatformChild.cpp62
-rw-r--r--accessible/ipc/win/PlatformChild.h35
-rw-r--r--accessible/ipc/win/ProxyAccessible.cpp599
-rw-r--r--accessible/ipc/win/ProxyAccessible.h58
-rw-r--r--accessible/ipc/win/moz.build39
-rw-r--r--accessible/ipc/win/typelib/Accessible.idl16
-rw-r--r--accessible/ipc/win/typelib/Makefile.in31
-rw-r--r--accessible/ipc/win/typelib/moz.build13
-rw-r--r--accessible/jsat/AccessFu.css59
-rw-r--r--accessible/jsat/AccessFu.jsm1000
-rw-r--r--accessible/jsat/Constants.jsm59
-rw-r--r--accessible/jsat/ContentControl.jsm528
-rw-r--r--accessible/jsat/EventManager.jsm723
-rw-r--r--accessible/jsat/Gestures.jsm956
-rw-r--r--accessible/jsat/OutputGenerator.jsm1003
-rw-r--r--accessible/jsat/PointerAdapter.jsm174
-rw-r--r--accessible/jsat/Presentation.jsm769
-rw-r--r--accessible/jsat/Traversal.jsm419
-rw-r--r--accessible/jsat/Utils.jsm1114
-rw-r--r--accessible/jsat/content-script.js151
-rw-r--r--accessible/jsat/jar.mn10
-rw-r--r--accessible/jsat/moz.build20
-rw-r--r--accessible/jsat/sounds/clicked.oggbin0 -> 6618 bytes
-rw-r--r--accessible/jsat/sounds/virtual_cursor_key.oggbin0 -> 4224 bytes
-rw-r--r--accessible/jsat/sounds/virtual_cursor_move.oggbin0 -> 5636 bytes
-rw-r--r--accessible/mac/ARIAGridAccessibleWrap.h22
-rw-r--r--accessible/mac/AccessibleWrap.h103
-rw-r--r--accessible/mac/AccessibleWrap.mm256
-rw-r--r--accessible/mac/ApplicationAccessibleWrap.h22
-rw-r--r--accessible/mac/DocAccessibleWrap.h25
-rw-r--r--accessible/mac/DocAccessibleWrap.mm21
-rw-r--r--accessible/mac/HTMLTableAccessibleWrap.h24
-rw-r--r--accessible/mac/HyperTextAccessibleWrap.h20
-rw-r--r--accessible/mac/ImageAccessibleWrap.h22
-rw-r--r--accessible/mac/MacUtils.h27
-rw-r--r--accessible/mac/MacUtils.mm33
-rw-r--r--accessible/mac/Platform.mm175
-rw-r--r--accessible/mac/RootAccessibleWrap.h34
-rw-r--r--accessible/mac/RootAccessibleWrap.mm53
-rw-r--r--accessible/mac/TextLeafAccessibleWrap.h19
-rw-r--r--accessible/mac/XULListboxAccessibleWrap.h20
-rw-r--r--accessible/mac/XULMenuAccessibleWrap.h19
-rw-r--r--accessible/mac/XULTreeGridAccessibleWrap.h20
-rw-r--r--accessible/mac/moz.build45
-rw-r--r--accessible/mac/mozAccessible.h181
-rw-r--r--accessible/mac/mozAccessible.mm1236
-rw-r--r--accessible/mac/mozAccessibleProtocol.h69
-rw-r--r--accessible/mac/mozActionElements.h37
-rw-r--r--accessible/mac/mozActionElements.mm340
-rw-r--r--accessible/mac/mozDocAccessible.h31
-rw-r--r--accessible/mac/mozDocAccessible.mm111
-rw-r--r--accessible/mac/mozHTMLAccessible.h16
-rw-r--r--accessible/mac/mozHTMLAccessible.mm139
-rw-r--r--accessible/mac/mozTableAccessible.h28
-rw-r--r--accessible/mac/mozTableAccessible.mm240
-rw-r--r--accessible/mac/mozTextAccessible.h17
-rw-r--r--accessible/mac/mozTextAccessible.mm626
-rw-r--r--accessible/moz.build36
-rw-r--r--accessible/other/ARIAGridAccessibleWrap.h23
-rw-r--r--accessible/other/AccessibleWrap.cpp25
-rw-r--r--accessible/other/AccessibleWrap.h29
-rw-r--r--accessible/other/ApplicationAccessibleWrap.h21
-rw-r--r--accessible/other/DocAccessibleWrap.h23
-rw-r--r--accessible/other/HTMLTableAccessibleWrap.h24
-rw-r--r--accessible/other/HyperTextAccessibleWrap.h20
-rw-r--r--accessible/other/ImageAccessibleWrap.h22
-rw-r--r--accessible/other/Platform.cpp61
-rw-r--r--accessible/other/RootAccessibleWrap.h23
-rw-r--r--accessible/other/TextLeafAccessibleWrap.h19
-rw-r--r--accessible/other/XULListboxAccessibleWrap.h20
-rw-r--r--accessible/other/XULMenuAccessibleWrap.h19
-rw-r--r--accessible/other/XULTreeGridAccessibleWrap.h20
-rw-r--r--accessible/other/moz.build27
-rw-r--r--accessible/tests/browser/.eslintrc.js218
-rw-r--r--accessible/tests/browser/browser.ini17
-rw-r--r--accessible/tests/browser/browser_shutdown_multi_reference.js48
-rw-r--r--accessible/tests/browser/browser_shutdown_parent_own_reference.js72
-rw-r--r--accessible/tests/browser/browser_shutdown_remote_no_reference.js48
-rw-r--r--accessible/tests/browser/browser_shutdown_remote_only.js40
-rw-r--r--accessible/tests/browser/browser_shutdown_remote_own_reference.js75
-rw-r--r--accessible/tests/browser/browser_shutdown_scope_lifecycle.js21
-rw-r--r--accessible/tests/browser/browser_shutdown_start_restart.js41
-rw-r--r--accessible/tests/browser/e10s/browser.ini51
-rw-r--r--accessible/tests/browser/e10s/browser_caching_attributes.js117
-rw-r--r--accessible/tests/browser/e10s/browser_caching_description.js164
-rw-r--r--accessible/tests/browser/e10s/browser_caching_name.js434
-rw-r--r--accessible/tests/browser/e10s/browser_caching_relations.js86
-rw-r--r--accessible/tests/browser/e10s/browser_caching_states.js120
-rw-r--r--accessible/tests/browser/e10s/browser_caching_value.js155
-rw-r--r--accessible/tests/browser/e10s/browser_events_caretmove.js21
-rw-r--r--accessible/tests/browser/e10s/browser_events_hide.js35
-rw-r--r--accessible/tests/browser/e10s/browser_events_show.js17
-rw-r--r--accessible/tests/browser/e10s/browser_events_statechange.js62
-rw-r--r--accessible/tests/browser/e10s/browser_events_textchange.js74
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_ariadialog.js43
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js318
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_canvas.js25
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_cssoverflow.js64
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_doc.js312
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_gencontent.js78
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_hidden.js30
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_imagemap.js176
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_list.js43
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_list_editabledoc.js39
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_listener.js29
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_optgroup.js91
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_removal.js39
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_table.js51
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_textleaf.js35
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_visibility.js196
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_whitespace.js71
-rw-r--r--accessible/tests/browser/e10s/doc_treeupdate_ariadialog.html23
-rw-r--r--accessible/tests/browser/e10s/doc_treeupdate_ariaowns.html44
-rw-r--r--accessible/tests/browser/e10s/doc_treeupdate_imagemap.html21
-rw-r--r--accessible/tests/browser/e10s/doc_treeupdate_removal.xhtml11
-rw-r--r--accessible/tests/browser/e10s/doc_treeupdate_visibility.html78
-rw-r--r--accessible/tests/browser/e10s/doc_treeupdate_whitespace.html10
-rw-r--r--accessible/tests/browser/e10s/events.js127
-rw-r--r--accessible/tests/browser/e10s/head.js84
-rw-r--r--accessible/tests/browser/head.js116
-rw-r--r--accessible/tests/browser/shared-head.js229
-rw-r--r--accessible/tests/crashtests/448064.xhtml73
-rw-r--r--accessible/tests/crashtests/471493.xul35
-rw-r--r--accessible/tests/crashtests/crashtests.list3
-rw-r--r--accessible/tests/mochitest/a11y.ini17
-rw-r--r--accessible/tests/mochitest/actions.js187
-rw-r--r--accessible/tests/mochitest/actions/a11y.ini18
-rw-r--r--accessible/tests/mochitest/actions/test_anchors.html150
-rw-r--r--accessible/tests/mochitest/actions/test_aria.html202
-rw-r--r--accessible/tests/mochitest/actions/test_controls.html109
-rw-r--r--accessible/tests/mochitest/actions/test_general.html107
-rw-r--r--accessible/tests/mochitest/actions/test_general.xul145
-rw-r--r--accessible/tests/mochitest/actions/test_keys.html60
-rw-r--r--accessible/tests/mochitest/actions/test_keys_menu.xul99
-rw-r--r--accessible/tests/mochitest/actions/test_link.html147
-rw-r--r--accessible/tests/mochitest/actions/test_media.html121
-rw-r--r--accessible/tests/mochitest/actions/test_select.html105
-rw-r--r--accessible/tests/mochitest/actions/test_tree.xul128
-rw-r--r--accessible/tests/mochitest/actions/test_treegrid.xul197
-rw-r--r--accessible/tests/mochitest/aom/a11y.ini3
-rw-r--r--accessible/tests/mochitest/aom/test_general.html55
-rw-r--r--accessible/tests/mochitest/attributes.js382
-rw-r--r--accessible/tests/mochitest/attributes/a11y.ini12
-rw-r--r--accessible/tests/mochitest/attributes/test_obj.html278
-rw-r--r--accessible/tests/mochitest/attributes/test_obj_css.html231
-rw-r--r--accessible/tests/mochitest/attributes/test_obj_css.xul73
-rw-r--r--accessible/tests/mochitest/attributes/test_obj_group.html469
-rw-r--r--accessible/tests/mochitest/attributes/test_obj_group.xul216
-rw-r--r--accessible/tests/mochitest/attributes/test_obj_group_tree.xul85
-rw-r--r--accessible/tests/mochitest/attributes/test_tag.html82
-rw-r--r--accessible/tests/mochitest/attributes/test_xml-roles.html251
-rw-r--r--accessible/tests/mochitest/autocomplete.js221
-rw-r--r--accessible/tests/mochitest/bounds/a11y.ini8
-rw-r--r--accessible/tests/mochitest/bounds/test_list.html81
-rw-r--r--accessible/tests/mochitest/bounds/test_select.html85
-rw-r--r--accessible/tests/mochitest/bounds/test_zoom.html96
-rw-r--r--accessible/tests/mochitest/bounds/test_zoom_text.html77
-rw-r--r--accessible/tests/mochitest/browser.js153
-rw-r--r--accessible/tests/mochitest/common.js952
-rw-r--r--accessible/tests/mochitest/dumbfile.zipbin0 -> 22 bytes
-rw-r--r--accessible/tests/mochitest/editabletext/a11y.ini7
-rw-r--r--accessible/tests/mochitest/editabletext/editabletext.js353
-rw-r--r--accessible/tests/mochitest/editabletext/test_1.html144
-rw-r--r--accessible/tests/mochitest/editabletext/test_2.html63
-rw-r--r--accessible/tests/mochitest/elm/a11y.ini16
-rw-r--r--accessible/tests/mochitest/elm/test_HTMLSpec.html1671
-rw-r--r--accessible/tests/mochitest/elm/test_MathMLSpec.html620
-rw-r--r--accessible/tests/mochitest/elm/test_canvas.html58
-rw-r--r--accessible/tests/mochitest/elm/test_figure.html62
-rw-r--r--accessible/tests/mochitest/elm/test_listbox.xul74
-rw-r--r--accessible/tests/mochitest/elm/test_nsApplicationAcc.html75
-rw-r--r--accessible/tests/mochitest/elm/test_plugin.html79
-rw-r--r--accessible/tests/mochitest/elm/test_shadowroot.html60
-rw-r--r--accessible/tests/mochitest/events.js2329
-rw-r--r--accessible/tests/mochitest/events/a11y.ini67
-rw-r--r--accessible/tests/mochitest/events/docload_wnd.html39
-rw-r--r--accessible/tests/mochitest/events/focus.html10
-rw-r--r--accessible/tests/mochitest/events/scroll.html181
-rw-r--r--accessible/tests/mochitest/events/test_aria_alert.html92
-rw-r--r--accessible/tests/mochitest/events/test_aria_menu.html285
-rw-r--r--accessible/tests/mochitest/events/test_aria_objattr.html118
-rw-r--r--accessible/tests/mochitest/events/test_aria_owns.html129
-rw-r--r--accessible/tests/mochitest/events/test_aria_statechange.html208
-rw-r--r--accessible/tests/mochitest/events/test_attrs.html90
-rw-r--r--accessible/tests/mochitest/events/test_bug1322593-2.html83
-rw-r--r--accessible/tests/mochitest/events/test_bug1322593.html80
-rw-r--r--accessible/tests/mochitest/events/test_caretmove.html140
-rw-r--r--accessible/tests/mochitest/events/test_caretmove.xul72
-rw-r--r--accessible/tests/mochitest/events/test_coalescence.html864
-rw-r--r--accessible/tests/mochitest/events/test_contextmenu.html139
-rw-r--r--accessible/tests/mochitest/events/test_descrchange.html85
-rw-r--r--accessible/tests/mochitest/events/test_docload.html360
-rw-r--r--accessible/tests/mochitest/events/test_docload.xul243
-rw-r--r--accessible/tests/mochitest/events/test_docload_aria.html83
-rw-r--r--accessible/tests/mochitest/events/test_dragndrop.html110
-rw-r--r--accessible/tests/mochitest/events/test_flush.html77
-rw-r--r--accessible/tests/mochitest/events/test_focus_aria_activedescendant.html120
-rw-r--r--accessible/tests/mochitest/events/test_focus_autocomplete.xul518
-rw-r--r--accessible/tests/mochitest/events/test_focus_browserui.xul149
-rw-r--r--accessible/tests/mochitest/events/test_focus_canvas.html61
-rw-r--r--accessible/tests/mochitest/events/test_focus_contextmenu.xul99
-rw-r--r--accessible/tests/mochitest/events/test_focus_controls.html75
-rw-r--r--accessible/tests/mochitest/events/test_focus_dialog.html164
-rw-r--r--accessible/tests/mochitest/events/test_focus_doc.html95
-rw-r--r--accessible/tests/mochitest/events/test_focus_general.html179
-rw-r--r--accessible/tests/mochitest/events/test_focus_general.xul179
-rw-r--r--accessible/tests/mochitest/events/test_focus_listcontrols.xul189
-rw-r--r--accessible/tests/mochitest/events/test_focus_menu.xul119
-rw-r--r--accessible/tests/mochitest/events/test_focus_name.html122
-rw-r--r--accessible/tests/mochitest/events/test_focus_selects.html118
-rw-r--r--accessible/tests/mochitest/events/test_focus_tabbox.xul103
-rw-r--r--accessible/tests/mochitest/events/test_focus_tree.xul122
-rw-r--r--accessible/tests/mochitest/events/test_fromUserInput.html127
-rw-r--r--accessible/tests/mochitest/events/test_label.xul177
-rw-r--r--accessible/tests/mochitest/events/test_menu.xul202
-rw-r--r--accessible/tests/mochitest/events/test_mutation.html632
-rw-r--r--accessible/tests/mochitest/events/test_mutation.xhtml97
-rw-r--r--accessible/tests/mochitest/events/test_namechange.html123
-rw-r--r--accessible/tests/mochitest/events/test_namechange.xul92
-rw-r--r--accessible/tests/mochitest/events/test_scroll.xul131
-rw-r--r--accessible/tests/mochitest/events/test_scroll_caret.xul91
-rw-r--r--accessible/tests/mochitest/events/test_selection.html118
-rw-r--r--accessible/tests/mochitest/events/test_selection.xul255
-rw-r--r--accessible/tests/mochitest/events/test_selection_aria.html127
-rw-r--r--accessible/tests/mochitest/events/test_statechange.html287
-rw-r--r--accessible/tests/mochitest/events/test_text.html339
-rw-r--r--accessible/tests/mochitest/events/test_text_alg.html249
-rw-r--r--accessible/tests/mochitest/events/test_textattrchange.html115
-rw-r--r--accessible/tests/mochitest/events/test_textselchange.html86
-rw-r--r--accessible/tests/mochitest/events/test_tree.xul348
-rw-r--r--accessible/tests/mochitest/events/test_valuechange.html255
-rw-r--r--accessible/tests/mochitest/focus/a11y.ini9
-rw-r--r--accessible/tests/mochitest/focus/test_focusedChild.html87
-rw-r--r--accessible/tests/mochitest/focus/test_takeFocus.html128
-rw-r--r--accessible/tests/mochitest/focus/test_takeFocus.xul106
-rw-r--r--accessible/tests/mochitest/formimage.pngbin0 -> 20105 bytes
-rw-r--r--accessible/tests/mochitest/grid.js149
-rw-r--r--accessible/tests/mochitest/hittest/a11y.ini14
-rw-r--r--accessible/tests/mochitest/hittest/test_browser.html63
-rw-r--r--accessible/tests/mochitest/hittest/test_canvas_hitregion.html88
-rw-r--r--accessible/tests/mochitest/hittest/test_general.html115
-rw-r--r--accessible/tests/mochitest/hittest/test_menu.xul134
-rw-r--r--accessible/tests/mochitest/hittest/test_shadowroot.html72
-rw-r--r--accessible/tests/mochitest/hittest/test_zoom.html61
-rw-r--r--accessible/tests/mochitest/hittest/test_zoom_text.html57
-rw-r--r--accessible/tests/mochitest/hittest/test_zoom_tree.xul100
-rw-r--r--accessible/tests/mochitest/hittest/zoom_tree.xul18
-rw-r--r--accessible/tests/mochitest/hyperlink/a11y.ini7
-rw-r--r--accessible/tests/mochitest/hyperlink/hyperlink.js42
-rw-r--r--accessible/tests/mochitest/hyperlink/test_general.html279
-rw-r--r--accessible/tests/mochitest/hyperlink/test_general.xul97
-rw-r--r--accessible/tests/mochitest/hypertext/a11y.ini7
-rw-r--r--accessible/tests/mochitest/hypertext/test_general.html156
-rw-r--r--accessible/tests/mochitest/hypertext/test_update.html236
-rw-r--r--accessible/tests/mochitest/jsat/a11y.ini30
-rw-r--r--accessible/tests/mochitest/jsat/doc_content_integration.html115
-rw-r--r--accessible/tests/mochitest/jsat/doc_content_text.html15
-rw-r--r--accessible/tests/mochitest/jsat/doc_traversal.html164
-rw-r--r--accessible/tests/mochitest/jsat/dom_helper.js209
-rw-r--r--accessible/tests/mochitest/jsat/gestures.json352
-rw-r--r--accessible/tests/mochitest/jsat/jsatcommon.js739
-rw-r--r--accessible/tests/mochitest/jsat/output.js114
-rw-r--r--accessible/tests/mochitest/jsat/test_alive.html81
-rw-r--r--accessible/tests/mochitest/jsat/test_content_integration.html343
-rw-r--r--accessible/tests/mochitest/jsat/test_content_text.html292
-rw-r--r--accessible/tests/mochitest/jsat/test_explicit_names.html191
-rw-r--r--accessible/tests/mochitest/jsat/test_gesture_tracker.html51
-rw-r--r--accessible/tests/mochitest/jsat/test_hints.html89
-rw-r--r--accessible/tests/mochitest/jsat/test_landmarks.html183
-rw-r--r--accessible/tests/mochitest/jsat/test_live_regions.html472
-rw-r--r--accessible/tests/mochitest/jsat/test_output.html673
-rw-r--r--accessible/tests/mochitest/jsat/test_output_mathml.html313
-rw-r--r--accessible/tests/mochitest/jsat/test_pointer_relay.html95
-rw-r--r--accessible/tests/mochitest/jsat/test_quicknav_modes.html107
-rw-r--r--accessible/tests/mochitest/jsat/test_tables.html579
-rw-r--r--accessible/tests/mochitest/jsat/test_traversal.html167
-rw-r--r--accessible/tests/mochitest/jsat/test_traversal_helper.html113
-rw-r--r--accessible/tests/mochitest/layout.js258
-rw-r--r--accessible/tests/mochitest/letters.gifbin0 -> 5596 bytes
-rw-r--r--accessible/tests/mochitest/longdesc_src.html8
-rw-r--r--accessible/tests/mochitest/moz.build37
-rw-r--r--accessible/tests/mochitest/moz.pngbin0 -> 1991 bytes
-rw-r--r--accessible/tests/mochitest/name.js33
-rw-r--r--accessible/tests/mochitest/name/a11y.ini20
-rw-r--r--accessible/tests/mochitest/name/general.css11
-rw-r--r--accessible/tests/mochitest/name/general.xbl32
-rw-r--r--accessible/tests/mochitest/name/markup.js382
-rw-r--r--accessible/tests/mochitest/name/markuprules.xml373
-rw-r--r--accessible/tests/mochitest/name/test_browserui.xul107
-rw-r--r--accessible/tests/mochitest/name/test_counterstyle.html153
-rw-r--r--accessible/tests/mochitest/name/test_general.html631
-rw-r--r--accessible/tests/mochitest/name/test_general.xul382
-rw-r--r--accessible/tests/mochitest/name/test_link.html89
-rw-r--r--accessible/tests/mochitest/name/test_list.html89
-rw-r--r--accessible/tests/mochitest/name/test_markup.html60
-rw-r--r--accessible/tests/mochitest/name/test_svg.html55
-rw-r--r--accessible/tests/mochitest/name/test_toolbaritem.xul84
-rw-r--r--accessible/tests/mochitest/name/test_tree.xul211
-rw-r--r--accessible/tests/mochitest/pivot.js551
-rw-r--r--accessible/tests/mochitest/pivot/a11y.ini8
-rw-r--r--accessible/tests/mochitest/pivot/doc_virtualcursor.html38
-rw-r--r--accessible/tests/mochitest/pivot/doc_virtualcursor_text.html29
-rw-r--r--accessible/tests/mochitest/pivot/test_virtualcursor.html129
-rw-r--r--accessible/tests/mochitest/pivot/test_virtualcursor_text.html241
-rw-r--r--accessible/tests/mochitest/relations.js192
-rw-r--r--accessible/tests/mochitest/relations/a11y.ini12
-rw-r--r--accessible/tests/mochitest/relations/test_bindings.xhtml103
-rw-r--r--accessible/tests/mochitest/relations/test_embeds.xul122
-rw-r--r--accessible/tests/mochitest/relations/test_general.html406
-rw-r--r--accessible/tests/mochitest/relations/test_general.xul238
-rw-r--r--accessible/tests/mochitest/relations/test_tabbrowser.xul103
-rw-r--r--accessible/tests/mochitest/relations/test_tree.xul106
-rw-r--r--accessible/tests/mochitest/relations/test_ui_modalprompt.html107
-rw-r--r--accessible/tests/mochitest/relations/test_update.html225
-rw-r--r--accessible/tests/mochitest/role.js178
-rw-r--r--accessible/tests/mochitest/role/a11y.ini10
-rw-r--r--accessible/tests/mochitest/role/test_aria.html345
-rw-r--r--accessible/tests/mochitest/role/test_aria.xul72
-rw-r--r--accessible/tests/mochitest/role/test_general.html186
-rw-r--r--accessible/tests/mochitest/role/test_general.xul57
-rw-r--r--accessible/tests/mochitest/role/test_svg.html70
-rw-r--r--accessible/tests/mochitest/scroll/a11y.ini6
-rw-r--r--accessible/tests/mochitest/scroll/test_zoom.html148
-rw-r--r--accessible/tests/mochitest/scroll/test_zoom_text.html158
-rw-r--r--accessible/tests/mochitest/selectable.js80
-rw-r--r--accessible/tests/mochitest/selectable/a11y.ini11
-rw-r--r--accessible/tests/mochitest/selectable/test_aria.html225
-rw-r--r--accessible/tests/mochitest/selectable/test_listbox.xul152
-rw-r--r--accessible/tests/mochitest/selectable/test_menu.xul78
-rw-r--r--accessible/tests/mochitest/selectable/test_menulist.xul96
-rw-r--r--accessible/tests/mochitest/selectable/test_select.html243
-rw-r--r--accessible/tests/mochitest/selectable/test_tree.xul188
-rw-r--r--accessible/tests/mochitest/states.js266
-rw-r--r--accessible/tests/mochitest/states/a11y.ini37
-rw-r--r--accessible/tests/mochitest/states/test_aria.html629
-rw-r--r--accessible/tests/mochitest/states/test_aria.xul60
-rw-r--r--accessible/tests/mochitest/states/test_aria_imgmap.html79
-rw-r--r--accessible/tests/mochitest/states/test_aria_widgetitems.html162
-rw-r--r--accessible/tests/mochitest/states/test_buttons.html85
-rw-r--r--accessible/tests/mochitest/states/test_controls.html53
-rw-r--r--accessible/tests/mochitest/states/test_controls.xul182
-rw-r--r--accessible/tests/mochitest/states/test_doc.html89
-rw-r--r--accessible/tests/mochitest/states/test_doc_busy.html79
-rw-r--r--accessible/tests/mochitest/states/test_docarticle.html80
-rw-r--r--accessible/tests/mochitest/states/test_editablebody.html46
-rw-r--r--accessible/tests/mochitest/states/test_expandable.xul118
-rw-r--r--accessible/tests/mochitest/states/test_frames.html95
-rw-r--r--accessible/tests/mochitest/states/test_inputs.html271
-rw-r--r--accessible/tests/mochitest/states/test_link.html144
-rw-r--r--accessible/tests/mochitest/states/test_popup.xul55
-rw-r--r--accessible/tests/mochitest/states/test_selects.html203
-rw-r--r--accessible/tests/mochitest/states/test_stale.html115
-rw-r--r--accessible/tests/mochitest/states/test_tabs.xul70
-rw-r--r--accessible/tests/mochitest/states/test_textbox.xul153
-rw-r--r--accessible/tests/mochitest/states/test_tree.xul152
-rw-r--r--accessible/tests/mochitest/states/test_visibility.html175
-rw-r--r--accessible/tests/mochitest/states/test_visibility.xul152
-rw-r--r--accessible/tests/mochitest/states/z_frames.html11
-rw-r--r--accessible/tests/mochitest/states/z_frames_article.html11
-rw-r--r--accessible/tests/mochitest/states/z_frames_checkbox.html11
-rw-r--r--accessible/tests/mochitest/states/z_frames_textbox.html11
-rw-r--r--accessible/tests/mochitest/states/z_frames_update.html22
-rw-r--r--accessible/tests/mochitest/table.js778
-rw-r--r--accessible/tests/mochitest/table/a11y.ini27
-rw-r--r--accessible/tests/mochitest/table/test_css_tables.html116
-rw-r--r--accessible/tests/mochitest/table/test_headers_ariagrid.html185
-rw-r--r--accessible/tests/mochitest/table/test_headers_ariatable.html96
-rw-r--r--accessible/tests/mochitest/table/test_headers_listbox.xul194
-rw-r--r--accessible/tests/mochitest/table/test_headers_table.html713
-rw-r--r--accessible/tests/mochitest/table/test_headers_tree.xul101
-rw-r--r--accessible/tests/mochitest/table/test_indexes_ariagrid.html139
-rw-r--r--accessible/tests/mochitest/table/test_indexes_listbox.xul85
-rw-r--r--accessible/tests/mochitest/table/test_indexes_table.html410
-rw-r--r--accessible/tests/mochitest/table/test_indexes_tree.xul71
-rw-r--r--accessible/tests/mochitest/table/test_layoutguess.html506
-rw-r--r--accessible/tests/mochitest/table/test_mtable.html128
-rw-r--r--accessible/tests/mochitest/table/test_sels_ariagrid.html161
-rw-r--r--accessible/tests/mochitest/table/test_sels_listbox.xul247
-rw-r--r--accessible/tests/mochitest/table/test_sels_table.html180
-rw-r--r--accessible/tests/mochitest/table/test_sels_tree.xul79
-rw-r--r--accessible/tests/mochitest/table/test_struct_ariagrid.html149
-rw-r--r--accessible/tests/mochitest/table/test_struct_ariatreegrid.html76
-rw-r--r--accessible/tests/mochitest/table/test_struct_listbox.xul117
-rw-r--r--accessible/tests/mochitest/table/test_struct_table.html203
-rw-r--r--accessible/tests/mochitest/table/test_struct_tree.xul74
-rw-r--r--accessible/tests/mochitest/table/test_table_1.html105
-rw-r--r--accessible/tests/mochitest/table/test_table_2.html89
-rw-r--r--accessible/tests/mochitest/test_OuterDocAccessible.html89
-rw-r--r--accessible/tests/mochitest/test_aria_token_attrs.html329
-rw-r--r--accessible/tests/mochitest/test_bug420863.html103
-rw-r--r--accessible/tests/mochitest/test_descr.html121
-rw-r--r--accessible/tests/mochitest/test_nsIAccessibleDocument.html96
-rw-r--r--accessible/tests/mochitest/test_nsIAccessibleImage.html202
-rw-r--r--accessible/tests/mochitest/text.js634
-rw-r--r--accessible/tests/mochitest/text/a11y.ini16
-rw-r--r--accessible/tests/mochitest/text/doc.html9
-rw-r--r--accessible/tests/mochitest/text/test_atcaretoffset.html455
-rw-r--r--accessible/tests/mochitest/text/test_charboundary.html140
-rw-r--r--accessible/tests/mochitest/text/test_doc.html42
-rw-r--r--accessible/tests/mochitest/text/test_dynamic.html88
-rw-r--r--accessible/tests/mochitest/text/test_general.xul80
-rw-r--r--accessible/tests/mochitest/text/test_gettext.html112
-rw-r--r--accessible/tests/mochitest/text/test_hypertext.html147
-rw-r--r--accessible/tests/mochitest/text/test_lineboundary.html265
-rw-r--r--accessible/tests/mochitest/text/test_passwords.html60
-rw-r--r--accessible/tests/mochitest/text/test_selection.html101
-rw-r--r--accessible/tests/mochitest/text/test_wordboundary.html291
-rw-r--r--accessible/tests/mochitest/text/test_words.html133
-rw-r--r--accessible/tests/mochitest/textattrs/a11y.ini7
-rw-r--r--accessible/tests/mochitest/textattrs/test_general.html735
-rw-r--r--accessible/tests/mochitest/textattrs/test_invalid.html62
-rw-r--r--accessible/tests/mochitest/textcaret/a11y.ini6
-rw-r--r--accessible/tests/mochitest/textcaret/test_browserui.xul67
-rw-r--r--accessible/tests/mochitest/textcaret/test_general.html183
-rw-r--r--accessible/tests/mochitest/textrange/a11y.ini7
-rw-r--r--accessible/tests/mochitest/textrange/test_general.html108
-rw-r--r--accessible/tests/mochitest/textrange/test_selection.html120
-rw-r--r--accessible/tests/mochitest/textselection/a11y.ini6
-rw-r--r--accessible/tests/mochitest/textselection/test_general.html221
-rw-r--r--accessible/tests/mochitest/textselection/test_userinput.html95
-rw-r--r--accessible/tests/mochitest/tree/a11y.ini51
-rw-r--r--accessible/tests/mochitest/tree/dockids.html30
-rw-r--r--accessible/tests/mochitest/tree/test_applicationacc.xul74
-rw-r--r--accessible/tests/mochitest/tree/test_aria_globals.html129
-rw-r--r--accessible/tests/mochitest/tree/test_aria_grid.html279
-rw-r--r--accessible/tests/mochitest/tree/test_aria_imgmap.html108
-rw-r--r--accessible/tests/mochitest/tree/test_aria_list.html92
-rw-r--r--accessible/tests/mochitest/tree/test_aria_menu.html93
-rw-r--r--accessible/tests/mochitest/tree/test_aria_owns.html187
-rw-r--r--accessible/tests/mochitest/tree/test_aria_presentation.html179
-rw-r--r--accessible/tests/mochitest/tree/test_aria_table.html63
-rw-r--r--accessible/tests/mochitest/tree/test_brokencontext.html265
-rw-r--r--accessible/tests/mochitest/tree/test_button.xul73
-rw-r--r--accessible/tests/mochitest/tree/test_canvas.html55
-rw-r--r--accessible/tests/mochitest/tree/test_combobox.xul291
-rw-r--r--accessible/tests/mochitest/tree/test_cssflexbox.html80
-rw-r--r--accessible/tests/mochitest/tree/test_cssoverflow.html146
-rw-r--r--accessible/tests/mochitest/tree/test_dochierarchy.html86
-rw-r--r--accessible/tests/mochitest/tree/test_dockids.html65
-rw-r--r--accessible/tests/mochitest/tree/test_filectrl.html58
-rw-r--r--accessible/tests/mochitest/tree/test_formctrl.html132
-rw-r--r--accessible/tests/mochitest/tree/test_formctrl.xul130
-rw-r--r--accessible/tests/mochitest/tree/test_gencontent.html71
-rw-r--r--accessible/tests/mochitest/tree/test_groupbox.xul64
-rw-r--r--accessible/tests/mochitest/tree/test_iframe.html52
-rw-r--r--accessible/tests/mochitest/tree/test_img.html88
-rw-r--r--accessible/tests/mochitest/tree/test_invalid_img.xhtml50
-rw-r--r--accessible/tests/mochitest/tree/test_invalidationlist.html57
-rw-r--r--accessible/tests/mochitest/tree/test_list.html247
-rw-r--r--accessible/tests/mochitest/tree/test_map.html83
-rw-r--r--accessible/tests/mochitest/tree/test_media.html84
-rw-r--r--accessible/tests/mochitest/tree/test_select.html139
-rw-r--r--accessible/tests/mochitest/tree/test_tabbox.xul99
-rw-r--r--accessible/tests/mochitest/tree/test_tabbrowser.xul255
-rw-r--r--accessible/tests/mochitest/tree/test_table.html282
-rw-r--r--accessible/tests/mochitest/tree/test_tree.xul182
-rw-r--r--accessible/tests/mochitest/tree/test_txtcntr.html234
-rw-r--r--accessible/tests/mochitest/tree/test_txtctrl.html173
-rw-r--r--accessible/tests/mochitest/tree/test_txtctrl.xul219
-rw-r--r--accessible/tests/mochitest/tree/wnd.xul8
-rw-r--r--accessible/tests/mochitest/treeupdate/a11y.ini41
-rw-r--r--accessible/tests/mochitest/treeupdate/test_ariadialog.html119
-rw-r--r--accessible/tests/mochitest/treeupdate/test_ariaowns.html693
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug1040735.html42
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug1100602.html114
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug1175913.html105
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug1189277.html86
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug1276857.html143
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug852150.xhtml59
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug883708.xhtml33
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug884251.xhtml21
-rw-r--r--accessible/tests/mochitest/treeupdate/test_bug895082.html51
-rw-r--r--accessible/tests/mochitest/treeupdate/test_canvas.html92
-rw-r--r--accessible/tests/mochitest/treeupdate/test_colorpicker.xul150
-rw-r--r--accessible/tests/mochitest/treeupdate/test_contextmenu.xul317
-rw-r--r--accessible/tests/mochitest/treeupdate/test_cssoverflow.html143
-rw-r--r--accessible/tests/mochitest/treeupdate/test_deck.xul109
-rw-r--r--accessible/tests/mochitest/treeupdate/test_doc.html466
-rw-r--r--accessible/tests/mochitest/treeupdate/test_gencontent.html160
-rw-r--r--accessible/tests/mochitest/treeupdate/test_general.html150
-rw-r--r--accessible/tests/mochitest/treeupdate/test_hidden.html135
-rw-r--r--accessible/tests/mochitest/treeupdate/test_imagemap.html442
-rw-r--r--accessible/tests/mochitest/treeupdate/test_list.html152
-rw-r--r--accessible/tests/mochitest/treeupdate/test_list_editabledoc.html106
-rw-r--r--accessible/tests/mochitest/treeupdate/test_listbox.xul180
-rw-r--r--accessible/tests/mochitest/treeupdate/test_menu.xul128
-rw-r--r--accessible/tests/mochitest/treeupdate/test_menubutton.xul198
-rw-r--r--accessible/tests/mochitest/treeupdate/test_optgroup.html137
-rw-r--r--accessible/tests/mochitest/treeupdate/test_recreation.html155
-rw-r--r--accessible/tests/mochitest/treeupdate/test_select.html130
-rw-r--r--accessible/tests/mochitest/treeupdate/test_shutdown.xul132
-rw-r--r--accessible/tests/mochitest/treeupdate/test_table.html81
-rw-r--r--accessible/tests/mochitest/treeupdate/test_textleaf.html180
-rw-r--r--accessible/tests/mochitest/treeupdate/test_visibility.html437
-rw-r--r--accessible/tests/mochitest/treeupdate/test_whitespace.html187
-rw-r--r--accessible/tests/mochitest/treeview.css15
-rw-r--r--accessible/tests/mochitest/treeview.js289
-rw-r--r--accessible/tests/mochitest/value.js32
-rw-r--r--accessible/tests/mochitest/value/a11y.ini9
-rw-r--r--accessible/tests/mochitest/value/test_general.html159
-rw-r--r--accessible/tests/mochitest/value/test_number.html59
-rw-r--r--accessible/tests/mochitest/value/test_progress.html61
-rw-r--r--accessible/tests/mochitest/value/test_progress.xul72
-rw-r--r--accessible/tests/mochitest/value/test_range.html59
-rw-r--r--accessible/windows/ProxyWrappers.h92
-rw-r--r--accessible/windows/ia2/ia2Accessible.cpp810
-rw-r--r--accessible/windows/ia2/ia2Accessible.h122
-rw-r--r--accessible/windows/ia2/ia2AccessibleAction.cpp188
-rw-r--r--accessible/windows/ia2/ia2AccessibleAction.h93
-rw-r--r--accessible/windows/ia2/ia2AccessibleComponent.cpp127
-rw-r--r--accessible/windows/ia2/ia2AccessibleComponent.h38
-rw-r--r--accessible/windows/ia2/ia2AccessibleEditableText.cpp153
-rw-r--r--accessible/windows/ia2/ia2AccessibleEditableText.h56
-rw-r--r--accessible/windows/ia2/ia2AccessibleHyperlink.cpp205
-rw-r--r--accessible/windows/ia2/ia2AccessibleHyperlink.h51
-rw-r--r--accessible/windows/ia2/ia2AccessibleHypertext.cpp93
-rw-r--r--accessible/windows/ia2/ia2AccessibleHypertext.h43
-rw-r--r--accessible/windows/ia2/ia2AccessibleImage.cpp119
-rw-r--r--accessible/windows/ia2/ia2AccessibleImage.h40
-rw-r--r--accessible/windows/ia2/ia2AccessibleRelation.cpp125
-rw-r--r--accessible/windows/ia2/ia2AccessibleRelation.h85
-rw-r--r--accessible/windows/ia2/ia2AccessibleTable.cpp747
-rw-r--r--accessible/windows/ia2/ia2AccessibleTable.h176
-rw-r--r--accessible/windows/ia2/ia2AccessibleTableCell.cpp257
-rw-r--r--accessible/windows/ia2/ia2AccessibleTableCell.h69
-rw-r--r--accessible/windows/ia2/ia2AccessibleText.cpp598
-rw-r--r--accessible/windows/ia2/ia2AccessibleText.h275
-rw-r--r--accessible/windows/ia2/ia2AccessibleValue.cpp151
-rw-r--r--accessible/windows/ia2/ia2AccessibleValue.h41
-rw-r--r--accessible/windows/ia2/moz.build58
-rw-r--r--accessible/windows/moz.build8
-rw-r--r--accessible/windows/msaa/ARIAGridAccessibleWrap.cpp47
-rw-r--r--accessible/windows/msaa/ARIAGridAccessibleWrap.h65
-rw-r--r--accessible/windows/msaa/AccessibleWrap.cpp1686
-rw-r--r--accessible/windows/msaa/AccessibleWrap.h265
-rw-r--r--accessible/windows/msaa/ApplicationAccessibleWrap.cpp162
-rw-r--r--accessible/windows/msaa/ApplicationAccessibleWrap.h52
-rw-r--r--accessible/windows/msaa/Compatibility.cpp112
-rw-r--r--accessible/windows/msaa/Compatibility.h79
-rw-r--r--accessible/windows/msaa/DocAccessibleWrap.cpp165
-rw-r--r--accessible/windows/msaa/DocAccessibleWrap.h65
-rw-r--r--accessible/windows/msaa/EnumVariant.cpp108
-rw-r--r--accessible/windows/msaa/EnumVariant.h60
-rw-r--r--accessible/windows/msaa/HTMLTableAccessibleWrap.cpp64
-rw-r--r--accessible/windows/msaa/HTMLTableAccessibleWrap.h93
-rw-r--r--accessible/windows/msaa/HTMLWin32ObjectAccessible.cpp109
-rw-r--r--accessible/windows/msaa/HTMLWin32ObjectAccessible.h70
-rw-r--r--accessible/windows/msaa/HyperTextAccessibleWrap.cpp67
-rw-r--r--accessible/windows/msaa/HyperTextAccessibleWrap.h46
-rw-r--r--accessible/windows/msaa/IDSet.h136
-rw-r--r--accessible/windows/msaa/IUnknownImpl.cpp58
-rw-r--r--accessible/windows/msaa/IUnknownImpl.h192
-rw-r--r--accessible/windows/msaa/ImageAccessibleWrap.cpp20
-rw-r--r--accessible/windows/msaa/ImageAccessibleWrap.h38
-rw-r--r--accessible/windows/msaa/MsaaIdGenerator.cpp243
-rw-r--r--accessible/windows/msaa/MsaaIdGenerator.h56
-rw-r--r--accessible/windows/msaa/Platform.cpp147
-rw-r--r--accessible/windows/msaa/RootAccessibleWrap.cpp45
-rw-r--r--accessible/windows/msaa/RootAccessibleWrap.h27
-rw-r--r--accessible/windows/msaa/ServiceProvider.cpp96
-rw-r--r--accessible/windows/msaa/ServiceProvider.h38
-rw-r--r--accessible/windows/msaa/TextLeafAccessibleWrap.cpp21
-rw-r--r--accessible/windows/msaa/TextLeafAccessibleWrap.h33
-rw-r--r--accessible/windows/msaa/XULListboxAccessibleWrap.cpp46
-rw-r--r--accessible/windows/msaa/XULListboxAccessibleWrap.h64
-rw-r--r--accessible/windows/msaa/XULMenuAccessibleWrap.cpp36
-rw-r--r--accessible/windows/msaa/XULMenuAccessibleWrap.h27
-rw-r--r--accessible/windows/msaa/XULTreeGridAccessibleWrap.cpp44
-rw-r--r--accessible/windows/msaa/XULTreeGridAccessibleWrap.h71
-rw-r--r--accessible/windows/msaa/moz.build76
-rw-r--r--accessible/windows/msaa/nsEventMap.h103
-rw-r--r--accessible/windows/msaa/nsWinUtils.cpp181
-rw-r--r--accessible/windows/msaa/nsWinUtils.h86
-rw-r--r--accessible/windows/sdn/moz.build24
-rw-r--r--accessible/windows/sdn/sdnAccessible-inl.h34
-rw-r--r--accessible/windows/sdn/sdnAccessible.cpp539
-rw-r--r--accessible/windows/sdn/sdnAccessible.h119
-rw-r--r--accessible/windows/sdn/sdnDocAccessible.cpp157
-rw-r--r--accessible/windows/sdn/sdnDocAccessible.h53
-rw-r--r--accessible/windows/sdn/sdnTextAccessible.cpp210
-rw-r--r--accessible/windows/sdn/sdnTextAccessible.h71
-rw-r--r--accessible/windows/uia/moz.build22
-rw-r--r--accessible/windows/uia/uiaRawElmProvider.cpp246
-rw-r--r--accessible/windows/uia/uiaRawElmProvider.h75
-rwxr-xr-xaccessible/xpcom/AccEventGen.py228
-rw-r--r--accessible/xpcom/AccEvents.conf18
-rw-r--r--accessible/xpcom/moz.build66
-rw-r--r--accessible/xpcom/nsAccessibleRelation.cpp79
-rw-r--r--accessible/xpcom/nsAccessibleRelation.h50
-rw-r--r--accessible/xpcom/xpcAccessibilityService.cpp246
-rw-r--r--accessible/xpcom/xpcAccessibilityService.h66
-rw-r--r--accessible/xpcom/xpcAccessible.cpp826
-rw-r--r--accessible/xpcom/xpcAccessible.h106
-rw-r--r--accessible/xpcom/xpcAccessibleApplication.cpp69
-rw-r--r--accessible/xpcom/xpcAccessibleApplication.h48
-rw-r--r--accessible/xpcom/xpcAccessibleDocument.cpp262
-rw-r--r--accessible/xpcom/xpcAccessibleDocument.h150
-rw-r--r--accessible/xpcom/xpcAccessibleGeneric.cpp46
-rw-r--r--accessible/xpcom/xpcAccessibleGeneric.h110
-rw-r--r--accessible/xpcom/xpcAccessibleHyperLink.cpp180
-rw-r--r--accessible/xpcom/xpcAccessibleHyperLink.h48
-rw-r--r--accessible/xpcom/xpcAccessibleHyperText.cpp822
-rw-r--r--accessible/xpcom/xpcAccessibleHyperText.h62
-rw-r--r--accessible/xpcom/xpcAccessibleImage.cpp55
-rw-r--r--accessible/xpcom/xpcAccessibleImage.h47
-rw-r--r--accessible/xpcom/xpcAccessibleSelectable.cpp134
-rw-r--r--accessible/xpcom/xpcAccessibleSelectable.h54
-rw-r--r--accessible/xpcom/xpcAccessibleTable.cpp493
-rw-r--r--accessible/xpcom/xpcAccessibleTable.h96
-rw-r--r--accessible/xpcom/xpcAccessibleTableCell.cpp161
-rw-r--r--accessible/xpcom/xpcAccessibleTableCell.h62
-rw-r--r--accessible/xpcom/xpcAccessibleTextRange.cpp221
-rw-r--r--accessible/xpcom/xpcAccessibleTextRange.h85
-rw-r--r--accessible/xpcom/xpcAccessibleValue.cpp133
-rw-r--r--accessible/xpcom/xpcAccessibleValue.h43
-rw-r--r--accessible/xul/XULAlertAccessible.cpp68
-rw-r--r--accessible/xul/XULAlertAccessible.h41
-rw-r--r--accessible/xul/XULColorPickerAccessible.cpp143
-rw-r--r--accessible/xul/XULColorPickerAccessible.h57
-rw-r--r--accessible/xul/XULComboboxAccessible.cpp194
-rw-r--r--accessible/xul/XULComboboxAccessible.h43
-rw-r--r--accessible/xul/XULElementAccessibles.cpp288
-rw-r--r--accessible/xul/XULElementAccessibles.h116
-rw-r--r--accessible/xul/XULFormControlAccessible.cpp633
-rw-r--r--accessible/xul/XULFormControlAccessible.h218
-rw-r--r--accessible/xul/XULListboxAccessible.cpp828
-rw-r--r--accessible/xul/XULListboxAccessible.h173
-rw-r--r--accessible/xul/XULMenuAccessible.cpp591
-rw-r--r--accessible/xul/XULMenuAccessible.h122
-rw-r--r--accessible/xul/XULSelectControlAccessible.cpp256
-rw-r--r--accessible/xul/XULSelectControlAccessible.h52
-rw-r--r--accessible/xul/XULSliderAccessible.cpp214
-rw-r--r--accessible/xul/XULSliderAccessible.h75
-rw-r--r--accessible/xul/XULTabAccessible.cpp209
-rw-r--r--accessible/xul/XULTabAccessible.h96
-rw-r--r--accessible/xul/XULTreeAccessible.cpp1184
-rw-r--r--accessible/xul/XULTreeAccessible.h279
-rw-r--r--accessible/xul/XULTreeGridAccessible.cpp823
-rw-r--r--accessible/xul/XULTreeGridAccessible.h187
-rw-r--r--accessible/xul/moz.build55
856 files changed, 160468 insertions, 0 deletions
diff --git a/accessible/.eslintrc.js b/accessible/.eslintrc.js
new file mode 100644
index 000000000..3cd32ed60
--- /dev/null
+++ b/accessible/.eslintrc.js
@@ -0,0 +1,17 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../.eslintrc.js"
+ ],
+ "globals": {
+ "Cc": true,
+ "Ci": true,
+ "Components": true,
+ "console": true,
+ "Cu": true,
+ "dump": true,
+ "Services": true,
+ "XPCOMUtils": true
+ }
+};
diff --git a/accessible/aom/AccessibleNode.cpp b/accessible/aom/AccessibleNode.cpp
new file mode 100644
index 000000000..dd26c6a29
--- /dev/null
+++ b/accessible/aom/AccessibleNode.cpp
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "AccessibleNode.h"
+#include "mozilla/dom/AccessibleNodeBinding.h"
+#include "mozilla/dom/BindingDeclarations.h"
+
+#include "Accessible-inl.h"
+#include "nsAccessibilityService.h"
+#include "DocAccessible.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(AccessibleNode)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AccessibleNode)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(AccessibleNode)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(AccessibleNode)
+
+AccessibleNode::AccessibleNode(nsINode* aNode) : mDOMNode(aNode)
+{
+ DocAccessible* doc =
+ GetOrCreateAccService()->GetDocAccessible(mDOMNode->OwnerDoc());
+ if (doc) {
+ mIntl = doc->GetAccessible(mDOMNode);
+ }
+}
+
+AccessibleNode::~AccessibleNode()
+{
+}
+
+/* virtual */ JSObject*
+AccessibleNode::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return AccessibleNodeBinding::Wrap(aCx, this, aGivenProto);
+}
+
+/* virtual */ ParentObject
+AccessibleNode::GetParentObject() const
+{
+ return mDOMNode->GetParentObject();
+}
+
+void
+AccessibleNode::GetRole(nsAString& aRole)
+{
+ if (mIntl) {
+ GetOrCreateAccService()->GetStringRole(mIntl->Role(), aRole);
+ return;
+ }
+
+ aRole.AssignLiteral("unknown");
+}
+
+nsINode*
+AccessibleNode::GetDOMNode()
+{
+ return mDOMNode;
+}
diff --git a/accessible/aom/AccessibleNode.h b/accessible/aom/AccessibleNode.h
new file mode 100644
index 000000000..355bb395c
--- /dev/null
+++ b/accessible/aom/AccessibleNode.h
@@ -0,0 +1,53 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=40: */
+/* 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 A11Y_AOM_ACCESSIBLENODE_H
+#define A11Y_AOM_ACCESSIBLENODE_H
+
+#include "nsWrapperCache.h"
+#include "mozilla/RefPtr.h"
+
+class nsINode;
+
+namespace mozilla {
+
+namespace a11y {
+ class Accessible;
+}
+
+namespace dom {
+
+struct ParentObject;
+
+class AccessibleNode : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ explicit AccessibleNode(nsINode* aNode);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS;
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AccessibleNode);
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override final;
+ virtual dom::ParentObject GetParentObject() const final;
+
+ void GetRole(nsAString& aRole);
+ nsINode* GetDOMNode();
+
+protected:
+ AccessibleNode(const AccessibleNode& aCopy) = delete;
+ AccessibleNode& operator=(const AccessibleNode& aCopy) = delete;
+ virtual ~AccessibleNode();
+
+ RefPtr<a11y::Accessible> mIntl;
+ RefPtr<nsINode> mDOMNode;
+};
+
+} // dom
+} // mozilla
+
+
+#endif // A11Y_JSAPI_ACCESSIBLENODE
diff --git a/accessible/aom/moz.build b/accessible/aom/moz.build
new file mode 100644
index 000000000..700081215
--- /dev/null
+++ b/accessible/aom/moz.build
@@ -0,0 +1,40 @@
+# -*- 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.dom += [
+ 'AccessibleNode.h',
+]
+
+UNIFIED_SOURCES += [
+ 'AccessibleNode.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/accessible/base',
+ '/accessible/generic',
+]
+
+if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ LOCAL_INCLUDES += [
+ '/accessible/atk',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ LOCAL_INCLUDES += [
+ '/accessible/windows/ia2',
+ '/accessible/windows/msaa',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ LOCAL_INCLUDES += [
+ '/accessible/mac',
+ ]
+else:
+ LOCAL_INCLUDES += [
+ '/accessible/other',
+ ]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
diff --git a/accessible/atk/ARIAGridAccessibleWrap.h b/accessible/atk/ARIAGridAccessibleWrap.h
new file mode 100644
index 000000000..8d53c6eb2
--- /dev/null
+++ b/accessible/atk/ARIAGridAccessibleWrap.h
@@ -0,0 +1,21 @@
+/* -*- 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_ARIAGRIDACCESSIBLEWRAP_H
+#define MOZILLA_A11Y_ARIAGRIDACCESSIBLEWRAP_H
+
+#include "ARIAGridAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class ARIAGridAccessible ARIAGridAccessibleWrap;
+typedef class ARIAGridCellAccessible ARIAGridCellAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/atk/AccessibleWrap.cpp b/accessible/atk/AccessibleWrap.cpp
new file mode 100644
index 000000000..cd0f2999a
--- /dev/null
+++ b/accessible/atk/AccessibleWrap.cpp
@@ -0,0 +1,1759 @@
+/* -*- 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 "Accessible-inl.h"
+#include "ApplicationAccessibleWrap.h"
+#include "InterfaceInitFuncs.h"
+#include "nsAccUtils.h"
+#include "mozilla/a11y/PDocAccessible.h"
+#include "OuterDocAccessible.h"
+#include "ProxyAccessible.h"
+#include "RootAccessible.h"
+#include "TableAccessible.h"
+#include "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 "nsXPCOMStrings.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIPersistentProperties2.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(accWrap);
+ g_object_set_qdata(G_OBJECT(this), quark_mai_hyperlink, maiHyperlink);
+ }
+
+ return maiHyperlink->GetAtkHyperlink();
+}
+
+void
+MaiAtkObject::Shutdown()
+{
+ accWrap.SetBits(0);
+ 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) :
+ Accessible(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();
+ Accessible::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, 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 Accessible interface
+/* static */
+AtkObject *
+AccessibleWrap::GetAtkObject(Accessible* 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)->accWrap.SetBits(reinterpret_cast<uintptr_t>(aData));
+}
+
+void
+finalizeCB(GObject *aObj)
+{
+ if (!IS_MAI_OBJECT(aObj))
+ return;
+ NS_ASSERTION(MAI_ATK_OBJECT(aObj)->accWrap.IsNull(), "AccWrap 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;
+ AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj);
+ if (accWrap)
+ accWrap->Name(name);
+ else if (ProxyAccessible* proxy = GetProxy(aAtkObj))
+ proxy->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;
+ AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj);
+ if (accWrap) {
+ if (accWrap->IsDefunct())
+ return nullptr;
+
+ accWrap->Description(uniDesc);
+ } else if (ProxyAccessible* proxy = GetProxy(aAtkObj)) {
+ proxy->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;
+
+ AccessibleOrProxy acc = GetInternalObj(aAtkObj);
+ if (acc.IsNull()) {
+ 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, atkRole, macRole, \
+ msaaRole, ia2Role, 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_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;
+
+ return aAtkObj->role;
+}
+
+static AtkAttributeSet*
+ConvertToAtkAttributeSet(nsIPersistentProperties* aAttributes)
+{
+ if (!aAttributes)
+ return nullptr;
+
+ AtkAttributeSet *objAttributeSet = nullptr;
+ nsCOMPtr<nsISimpleEnumerator> propEnum;
+ nsresult rv = aAttributes->Enumerate(getter_AddRefs(propEnum));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(propEnum->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> sup;
+ rv = propEnum->GetNext(getter_AddRefs(sup));
+ NS_ENSURE_SUCCESS(rv, objAttributeSet);
+
+ nsCOMPtr<nsIPropertyElement> propElem(do_QueryInterface(sup));
+ NS_ENSURE_TRUE(propElem, objAttributeSet);
+
+ nsAutoCString name;
+ rv = propElem->GetKey(name);
+ NS_ENSURE_SUCCESS(rv, objAttributeSet);
+
+ nsAutoString value;
+ rv = propElem->GetValue(value);
+ NS_ENSURE_SUCCESS(rv, objAttributeSet);
+
+ AtkAttribute *objAttr = (AtkAttribute *)g_malloc(sizeof(AtkAttribute));
+ objAttr->name = g_strdup(name.get());
+ objAttr->value = g_strdup(NS_ConvertUTF16toUTF8(value).get());
+ objAttributeSet = g_slist_prepend(objAttributeSet, objAttr);
+ }
+
+ //libspi will free it
+ return objAttributeSet;
+}
+
+AtkAttributeSet*
+GetAttributeSet(Accessible* aAccessible)
+{
+ nsCOMPtr<nsIPersistentProperties> attributes = aAccessible->Attributes();
+ if (attributes) {
+ // There is no ATK state for haspopup, must use object attribute to expose
+ // the same info.
+ if (aAccessible->State() & states::HASPOPUP) {
+ nsAutoString unused;
+ attributes->SetStringProperty(NS_LITERAL_CSTRING("haspopup"),
+ NS_LITERAL_STRING("true"), unused);
+ }
+
+ return ConvertToAtkAttributeSet(attributes);
+ }
+
+ return nullptr;
+}
+
+AtkAttributeSet *
+getAttributesCB(AtkObject *aAtkObj)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj);
+ if (accWrap)
+ return GetAttributeSet(accWrap);
+
+ ProxyAccessible* proxy = GetProxy(aAtkObj);
+ if (!proxy)
+ return nullptr;
+
+ AutoTArray<Attribute, 10> attrs;
+ proxy->Attributes(&attrs);
+ if (attrs.IsEmpty())
+ return nullptr;
+
+ AtkAttributeSet* objAttributeSet = nullptr;
+ for (uint32_t i = 0; i < attrs.Length(); i++) {
+ AtkAttribute *objAttr = (AtkAttribute *)g_malloc(sizeof(AtkAttribute));
+ objAttr->name = g_strdup(attrs[i].Name().get());
+ objAttr->value = g_strdup(NS_ConvertUTF16toUTF8(attrs[i].Value()).get());
+ objAttributeSet = g_slist_prepend(objAttributeSet, objAttr);
+ }
+
+ return objAttributeSet;
+}
+
+const gchar*
+GetLocaleCB(AtkObject* aAtkObj)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj);
+ if (!accWrap)
+ return nullptr;
+
+ nsAutoString locale;
+ accWrap->Language(locale);
+ return AccessibleWrap::ReturnString(locale);
+}
+
+AtkObject *
+getParentCB(AtkObject *aAtkObj)
+{
+ if (aAtkObj->accessible_parent)
+ return aAtkObj->accessible_parent;
+
+ AccessibleOrProxy acc = GetInternalObj(aAtkObj);
+ if (acc.IsNull()) {
+ return nullptr;
+ }
+
+ AccessibleOrProxy parent = acc.Parent();
+ AtkObject* atkParent = !parent.IsNull() ? GetWrapperFor(parent) : nullptr;
+ if (atkParent)
+ atk_object_set_parent(aAtkObj, atkParent);
+
+ return aAtkObj->accessible_parent;
+}
+
+gint
+getChildCountCB(AtkObject *aAtkObj)
+{
+ if (AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj)) {
+ if (nsAccUtils::MustPrune(accWrap)) {
+ return 0;
+ }
+
+ uint32_t count = accWrap->EmbeddedChildCount();
+ if (count) {
+ return static_cast<gint>(count);
+ }
+
+ OuterDocAccessible* outerDoc = accWrap->AsOuterDoc();
+ if (outerDoc && outerDoc->RemoteChildDoc()) {
+ return 1;
+ }
+ }
+
+ ProxyAccessible* proxy = GetProxy(aAtkObj);
+ if (proxy && !proxy->MustPruneChildren()) {
+ return proxy->EmbeddedChildCount();
+ }
+
+ return 0;
+}
+
+AtkObject *
+refChildCB(AtkObject *aAtkObj, gint aChildIndex)
+{
+ // aChildIndex should not be less than zero
+ if (aChildIndex < 0) {
+ return nullptr;
+ }
+
+ AtkObject* childAtkObj = nullptr;
+ AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj);
+ if (accWrap) {
+ if (nsAccUtils::MustPrune(accWrap)) {
+ return nullptr;
+ }
+
+ Accessible* accChild = accWrap->GetEmbeddedChildAt(aChildIndex);
+ if (accChild) {
+ childAtkObj = AccessibleWrap::GetAtkObject(accChild);
+ } else {
+ OuterDocAccessible* docOwner = accWrap->AsOuterDoc();
+ if (docOwner) {
+ ProxyAccessible* proxyDoc = docOwner->RemoteChildDoc();
+ if (proxyDoc)
+ childAtkObj = GetWrapperFor(proxyDoc);
+ }
+ }
+ } else if (ProxyAccessible* proxy = GetProxy(aAtkObj)) {
+ if (proxy->MustPruneChildren())
+ return nullptr;
+
+ ProxyAccessible* child = proxy->EmbeddedChildAt(aChildIndex);
+ if (child)
+ childAtkObj = GetWrapperFor(child);
+ } else {
+ return nullptr;
+ }
+
+ 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 Accessible::IndexInParent() because we don't include text
+ // leaf nodes as children in ATK.
+ if (ProxyAccessible* proxy = GetProxy(aAtkObj)) {
+ if (ProxyAccessible* parent = proxy->Parent())
+ return parent->IndexOfEmbeddedChild(proxy);
+
+ if (proxy->OuterDocOfRemoteBrowser())
+ return 0;
+
+ return -1;
+ }
+
+ AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj);
+ if (!accWrap) {
+ return -1;
+ }
+
+ Accessible* parent = accWrap->Parent();
+ if (!parent)
+ return -1; // No parent
+
+ return parent->GetIndexOfEmbeddedChild(accWrap);
+}
+
+static void
+TranslateStates(uint64_t aState, AtkStateSet* aStateSet)
+{
+ // atk doesn't have a read only state so read only things shouldn't be
+ // editable.
+ if (aState & states::READONLY)
+ aState &= ~states::EDITABLE;
+
+ // Convert every state to an entry in AtkStateMap
+ uint32_t stateIndex = 0;
+ uint64_t bitMask = 1;
+ while (gAtkStateMap[stateIndex].stateMapEntryType != kNoSuchState) {
+ 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;
+ ++ stateIndex;
+ }
+}
+
+AtkStateSet *
+refStateSetCB(AtkObject *aAtkObj)
+{
+ AtkStateSet *state_set = nullptr;
+ state_set = ATK_OBJECT_CLASS(parent_class)->ref_state_set(aAtkObj);
+
+ AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj);
+ if (accWrap)
+ TranslateStates(accWrap->State(), state_set);
+ else if (ProxyAccessible* proxy = GetProxy(aAtkObj))
+ TranslateStates(proxy->State(), state_set);
+ else
+ TranslateStates(states::DEFUNCT, 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(AccessibleWrap::GetAtkObject(tempAcc));
+
+ if (aType == RelationType::EMBEDS && aAcc->IsRoot()) {
+ if (ProxyAccessible* proxyDoc =
+ aAcc->AsRoot()->GetPrimaryRemoteTopLevelContentDoc()) {
+ targets.AppendElement(GetWrapperFor(proxyDoc));
+ }
+ }
+
+ 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);
+
+ const AtkRelationType typeMap[] = {
+#define RELATIONTYPE(gecko, s, atk, m, i) atk,
+#include "RelationTypeMap.h"
+#undef RELATIONTYPE
+ };
+
+ if (ProxyAccessible* proxy = GetProxy(aAtkObj)) {
+ nsTArray<RelationType> types;
+ nsTArray<nsTArray<ProxyAccessible*>> targetSets;
+ proxy->Relations(&types, &targetSets);
+
+ size_t relationCount = types.Length();
+ for (size_t i = 0; i < relationCount; i++) {
+ if (typeMap[static_cast<uint32_t>(types[i])] == ATK_RELATION_NULL)
+ continue;
+
+ size_t targetCount = targetSets[i].Length();
+ AutoTArray<AtkObject*, 5> wrappers;
+ for (size_t j = 0; j < targetCount; j++)
+ wrappers.AppendElement(GetWrapperFor(targetSets[i][j]));
+
+ AtkRelationType atkType = typeMap[static_cast<uint32_t>(types[i])];
+ AtkRelation* atkRelation =
+ atk_relation_set_get_relation_by_type(relation_set, atkType);
+ if (atkRelation)
+ atk_relation_set_remove(relation_set, atkRelation);
+
+ atkRelation = atk_relation_new(wrappers.Elements(), wrappers.Length(),
+ atkType);
+ atk_relation_set_add(relation_set, atkRelation);
+ g_object_unref(atkRelation);
+ }
+ }
+
+ AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj);
+ if (!accWrap)
+ return relation_set;
+
+#define RELATIONTYPE(geckoType, geckoTypeName, atkType, msaaType, ia2Type) \
+ UpdateAtkRelation(RelationType::geckoType, accWrap, 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)
+{
+ bool isMAIObject = IS_MAI_OBJECT(aAtkObj);
+ NS_ENSURE_TRUE(isMAIObject || MAI_IS_ATK_SOCKET(aAtkObj),
+ nullptr);
+
+ AccessibleWrap* accWrap = nullptr;
+ if (isMAIObject) {
+ Accessible* acc = MAI_ATK_OBJECT(aAtkObj)->accWrap.AsAccessible();
+ accWrap = static_cast<AccessibleWrap*>(acc);
+ } else {
+ accWrap = MAI_ATK_SOCKET(aAtkObj)->accWrap;
+ }
+
+ // 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;
+}
+
+ProxyAccessible*
+GetProxy(AtkObject* aObj)
+{
+ return GetInternalObj(aObj).AsProxy();
+}
+
+AccessibleOrProxy
+GetInternalObj(AtkObject* aObj)
+{
+ if (!aObj || !IS_MAI_OBJECT(aObj))
+ return nullptr;
+
+ return MAI_ATK_OBJECT(aObj)->accWrap;
+}
+
+AtkObject*
+GetWrapperFor(ProxyAccessible* aProxy)
+{
+ return reinterpret_cast<AtkObject*>(aProxy->GetWrapper() & ~IS_PROXY);
+}
+
+AtkObject*
+GetWrapperFor(AccessibleOrProxy aObj)
+{
+ if (aObj.IsProxy()) {
+ return GetWrapperFor(aObj.AsProxy());
+ }
+
+ return AccessibleWrap::GetAtkObject(aObj.AsAccessible());
+}
+
+static uint16_t
+GetInterfacesForProxy(ProxyAccessible* aProxy, uint32_t aInterfaces)
+{
+ uint16_t interfaces = 1 << MAI_INTERFACE_COMPONENT;
+ if (aInterfaces & Interfaces::HYPERTEXT)
+ interfaces |= (1 << MAI_INTERFACE_HYPERTEXT) | (1 << MAI_INTERFACE_TEXT)
+ | (1 << MAI_INTERFACE_EDITABLE_TEXT);
+
+ if (aInterfaces & Interfaces::HYPERLINK)
+ interfaces |= 1 << MAI_INTERFACE_HYPERLINK_IMPL;
+
+ if (aInterfaces & Interfaces::VALUE)
+ interfaces |= 1 << MAI_INTERFACE_VALUE;
+
+ if (aInterfaces & Interfaces::TABLE)
+ interfaces |= 1 << MAI_INTERFACE_TABLE;
+
+ if (aInterfaces & Interfaces::TABLECELL)
+ interfaces |= 1 << MAI_INTERFACE_TABLE_CELL;
+
+ if (aInterfaces & Interfaces::IMAGE)
+ interfaces |= 1 << MAI_INTERFACE_IMAGE;
+
+ if (aInterfaces & Interfaces::DOCUMENT)
+ interfaces |= 1 << MAI_INTERFACE_DOCUMENT;
+
+ if (aInterfaces & Interfaces::SELECTION) {
+ interfaces |= 1 << MAI_INTERFACE_SELECTION;
+ }
+
+ if (aInterfaces & Interfaces::ACTION) {
+ interfaces |= 1 << MAI_INTERFACE_ACTION;
+ }
+
+ return interfaces;
+}
+
+void
+a11y::ProxyCreated(ProxyAccessible* aProxy, uint32_t aInterfaces)
+{
+ GType type = GetMaiAtkType(GetInterfacesForProxy(aProxy, aInterfaces));
+ NS_ASSERTION(type, "why don't we have a type!");
+
+ AtkObject* obj =
+ reinterpret_cast<AtkObject *>
+ (g_object_new(type, nullptr));
+ if (!obj)
+ return;
+
+ uintptr_t inner = reinterpret_cast<uintptr_t>(aProxy) | IS_PROXY;
+ atk_object_initialize(obj, reinterpret_cast<gpointer>(inner));
+ obj->role = ATK_ROLE_INVALID;
+ obj->layer = ATK_LAYER_INVALID;
+ aProxy->SetWrapper(reinterpret_cast<uintptr_t>(obj) | IS_PROXY);
+}
+
+void
+a11y::ProxyDestroyed(ProxyAccessible* aProxy)
+{
+ auto obj = reinterpret_cast<MaiAtkObject*>(aProxy->GetWrapper() & ~IS_PROXY);
+ if (!obj) {
+ return;
+ }
+
+ obj->Shutdown();
+ g_object_unref(obj);
+ aProxy->SetWrapper(0);
+}
+
+nsresult
+AccessibleWrap::HandleAccEvent(AccEvent* aEvent)
+{
+ nsresult rv = Accessible::HandleAccEvent(aEvent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (IPCAccessibilityActive()) {
+ return NS_OK;
+ }
+
+ Accessible* accessible = aEvent->GetAccessible();
+ NS_ENSURE_TRUE(accessible, NS_ERROR_FAILURE);
+
+ // The accessible can become defunct if we have an xpcom event listener
+ // which decides it would be fun to change the DOM and flush layout.
+ if (accessible->IsDefunct())
+ return NS_OK;
+
+ uint32_t type = aEvent->GetEventType();
+
+ AtkObject* atkObj = AccessibleWrap::GetAtkObject(accessible);
+
+ // We don't create ATK objects for plain text leaves, just return NS_OK in
+ // such case.
+ if (!atkObj) {
+ NS_ASSERTION(type == nsIAccessibleEvent::EVENT_SHOW ||
+ type == nsIAccessibleEvent::EVENT_HIDE,
+ "Event other than SHOW and HIDE fired for plain text leaves");
+ return NS_OK;
+ }
+
+ AccessibleWrap* accWrap = GetAccessibleWrap(atkObj);
+ if (!accWrap) {
+ return NS_OK; // Node is shut down
+ }
+
+ switch (type) {
+ case nsIAccessibleEvent::EVENT_STATE_CHANGE:
+ {
+ AccStateChangeEvent* event = downcast_accEvent(aEvent);
+ MAI_ATK_OBJECT(atkObj)->FireStateChangeEvent(event->GetState(),
+ event->IsStateEnabled());
+ break;
+ }
+
+ case nsIAccessibleEvent::EVENT_TEXT_REMOVED:
+ case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
+ {
+ AccTextChangeEvent* event = downcast_accEvent(aEvent);
+ NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
+
+ MAI_ATK_OBJECT(atkObj)-> FireTextChangeEvent(event->ModifiedText(),
+ event->GetStartOffset(),
+ event->GetLength(),
+ event->IsTextInserted(),
+ event->IsFromUserInput());
+
+ return NS_OK;
+ }
+
+ case nsIAccessibleEvent::EVENT_FOCUS:
+ {
+ a11y::RootAccessible* rootAccWrap = accWrap->RootAccessible();
+ if (rootAccWrap && rootAccWrap->mActivated) {
+ atk_focus_tracker_notify(atkObj);
+ // Fire state change event for focus
+ atk_object_notify_state_change(atkObj, ATK_STATE_FOCUSED, true);
+ return NS_OK;
+ }
+ } break;
+
+ case nsIAccessibleEvent::EVENT_NAME_CHANGE:
+ {
+ nsAutoString newName;
+ accessible->Name(newName);
+
+ MaybeFireNameChange(atkObj, newName);
+
+ break;
+ }
+
+ case nsIAccessibleEvent::EVENT_VALUE_CHANGE:
+ case nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE:
+ if (accessible->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*)atkObj, "accessible-value");
+ }
+ break;
+
+ case nsIAccessibleEvent::EVENT_SELECTION:
+ case nsIAccessibleEvent::EVENT_SELECTION_ADD:
+ case nsIAccessibleEvent::EVENT_SELECTION_REMOVE:
+ {
+ // XXX: dupe events may be fired
+ AccSelChangeEvent* selChangeEvent = downcast_accEvent(aEvent);
+ g_signal_emit_by_name(AccessibleWrap::GetAtkObject(selChangeEvent->Widget()),
+ "selection_changed");
+ break;
+ }
+
+ case nsIAccessibleEvent::EVENT_SELECTION_WITHIN:
+ {
+ g_signal_emit_by_name(atkObj, "selection_changed");
+ break;
+ }
+
+ case nsIAccessibleEvent::EVENT_ALERT:
+ // A hack using state change showing events as alert events.
+ atk_object_notify_state_change(atkObj, ATK_STATE_SHOWING, true);
+ break;
+
+ case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED:
+ g_signal_emit_by_name(atkObj, "text_selection_changed");
+ break;
+
+ case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED:
+ {
+ AccCaretMoveEvent* caretMoveEvent = downcast_accEvent(aEvent);
+ NS_ASSERTION(caretMoveEvent, "Event needs event data");
+ if (!caretMoveEvent)
+ break;
+
+ int32_t caretOffset = caretMoveEvent->GetCaretOffset();
+ g_signal_emit_by_name(atkObj, "text_caret_moved", caretOffset);
+ } break;
+
+ case nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED:
+ g_signal_emit_by_name(atkObj, "text-attributes-changed");
+ break;
+
+ case nsIAccessibleEvent::EVENT_TABLE_MODEL_CHANGED:
+ g_signal_emit_by_name(atkObj, "model_changed");
+ break;
+
+ case nsIAccessibleEvent::EVENT_TABLE_ROW_INSERT:
+ {
+ AccTableChangeEvent* tableEvent = downcast_accEvent(aEvent);
+ NS_ENSURE_TRUE(tableEvent, NS_ERROR_FAILURE);
+
+ int32_t rowIndex = tableEvent->GetIndex();
+ int32_t numRows = tableEvent->GetCount();
+
+ g_signal_emit_by_name(atkObj, "row_inserted", rowIndex, numRows);
+ } break;
+
+ case nsIAccessibleEvent::EVENT_TABLE_ROW_DELETE:
+ {
+ AccTableChangeEvent* tableEvent = downcast_accEvent(aEvent);
+ NS_ENSURE_TRUE(tableEvent, NS_ERROR_FAILURE);
+
+ int32_t rowIndex = tableEvent->GetIndex();
+ int32_t numRows = tableEvent->GetCount();
+
+ g_signal_emit_by_name(atkObj, "row_deleted", rowIndex, numRows);
+ } break;
+
+ case nsIAccessibleEvent::EVENT_TABLE_ROW_REORDER:
+ {
+ g_signal_emit_by_name(atkObj, "row_reordered");
+ break;
+ }
+
+ case nsIAccessibleEvent::EVENT_TABLE_COLUMN_INSERT:
+ {
+ AccTableChangeEvent* tableEvent = downcast_accEvent(aEvent);
+ NS_ENSURE_TRUE(tableEvent, NS_ERROR_FAILURE);
+
+ int32_t colIndex = tableEvent->GetIndex();
+ int32_t numCols = tableEvent->GetCount();
+ g_signal_emit_by_name(atkObj, "column_inserted", colIndex, numCols);
+ } break;
+
+ case nsIAccessibleEvent::EVENT_TABLE_COLUMN_DELETE:
+ {
+ AccTableChangeEvent* tableEvent = downcast_accEvent(aEvent);
+ NS_ENSURE_TRUE(tableEvent, NS_ERROR_FAILURE);
+
+ int32_t colIndex = tableEvent->GetIndex();
+ int32_t numCols = tableEvent->GetCount();
+ g_signal_emit_by_name(atkObj, "column_deleted", colIndex, numCols);
+ } break;
+
+ case nsIAccessibleEvent::EVENT_TABLE_COLUMN_REORDER:
+ g_signal_emit_by_name(atkObj, "column_reordered");
+ break;
+
+ case nsIAccessibleEvent::EVENT_SECTION_CHANGED:
+ g_signal_emit_by_name(atkObj, "visible_data_changed");
+ break;
+
+ case nsIAccessibleEvent::EVENT_SHOW:
+ {
+ AccMutationEvent* event = downcast_accEvent(aEvent);
+ Accessible* parentAcc = event ? event->Parent() : accessible->Parent();
+ AtkObject* parent = AccessibleWrap::GetAtkObject(parentAcc);
+ NS_ENSURE_STATE(parent);
+ auto obj = reinterpret_cast<MaiAtkObject*>(atkObj);
+ obj->FireAtkShowHideEvent(parent, true, aEvent->IsFromUserInput());
+ return NS_OK;
+ }
+
+ case nsIAccessibleEvent::EVENT_HIDE:
+ {
+ // XXX - Handle native dialog accessibles.
+ if (!accessible->IsRoot() && accessible->HasARIARole() &&
+ accessible->ARIARole() == roles::DIALOG) {
+ guint id = g_signal_lookup("deactivate", MAI_TYPE_ATK_OBJECT);
+ g_signal_emit(atkObj, id, 0);
+ }
+
+ AccMutationEvent* event = downcast_accEvent(aEvent);
+ Accessible* parentAcc = event ? event->Parent() : accessible->Parent();
+ AtkObject* parent = AccessibleWrap::GetAtkObject(parentAcc);
+ NS_ENSURE_STATE(parent);
+ auto obj = reinterpret_cast<MaiAtkObject*>(atkObj);
+ obj->FireAtkShowHideEvent(parent, false, aEvent->IsFromUserInput());
+ return NS_OK;
+ }
+
+ /*
+ * Because dealing with menu is very different between nsIAccessible
+ * and ATK, and the menu activity is important, specially transfer the
+ * following two event.
+ * Need more verification by AT test.
+ */
+ case nsIAccessibleEvent::EVENT_MENU_START:
+ case nsIAccessibleEvent::EVENT_MENU_END:
+ break;
+
+ case nsIAccessibleEvent::EVENT_WINDOW_ACTIVATE:
+ {
+ accessible->AsRoot()->mActivated = true;
+ guint id = g_signal_lookup("activate", MAI_TYPE_ATK_OBJECT);
+ g_signal_emit(atkObj, id, 0);
+
+ // Always fire a current focus event after activation.
+ FocusMgr()->ForceFocusEvent();
+ } break;
+
+ case nsIAccessibleEvent::EVENT_WINDOW_DEACTIVATE:
+ {
+ accessible->AsRoot()->mActivated = false;
+ guint id = g_signal_lookup("deactivate", MAI_TYPE_ATK_OBJECT);
+ g_signal_emit(atkObj, id, 0);
+ } break;
+
+ case nsIAccessibleEvent::EVENT_WINDOW_MAXIMIZE:
+ {
+ guint id = g_signal_lookup("maximize", MAI_TYPE_ATK_OBJECT);
+ g_signal_emit(atkObj, id, 0);
+ } break;
+
+ case nsIAccessibleEvent::EVENT_WINDOW_MINIMIZE:
+ {
+ guint id = g_signal_lookup("minimize", MAI_TYPE_ATK_OBJECT);
+ g_signal_emit(atkObj, id, 0);
+ } break;
+
+ case nsIAccessibleEvent::EVENT_WINDOW_RESTORE:
+ {
+ guint id = g_signal_lookup("restore", MAI_TYPE_ATK_OBJECT);
+ g_signal_emit(atkObj, id, 0);
+ } break;
+
+ case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE:
+ g_signal_emit_by_name (atkObj, "load_complete");
+ // XXX - Handle native dialog accessibles.
+ if (!accessible->IsRoot() && accessible->HasARIARole() &&
+ accessible->ARIARole() == roles::DIALOG) {
+ guint id = g_signal_lookup("activate", MAI_TYPE_ATK_OBJECT);
+ g_signal_emit(atkObj, id, 0);
+ }
+ break;
+
+ case nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD:
+ g_signal_emit_by_name (atkObj, "reload");
+ break;
+
+ case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED:
+ g_signal_emit_by_name (atkObj, "load_stopped");
+ break;
+
+ case nsIAccessibleEvent::EVENT_MENUPOPUP_START:
+ atk_focus_tracker_notify(atkObj); // fire extra focus event
+ atk_object_notify_state_change(atkObj, ATK_STATE_VISIBLE, true);
+ atk_object_notify_state_change(atkObj, ATK_STATE_SHOWING, true);
+ break;
+
+ case nsIAccessibleEvent::EVENT_MENUPOPUP_END:
+ atk_object_notify_state_change(atkObj, ATK_STATE_VISIBLE, false);
+ atk_object_notify_state_change(atkObj, ATK_STATE_SHOWING, false);
+ break;
+ }
+
+ return NS_OK;
+}
+
+void
+a11y::ProxyEvent(ProxyAccessible* aTarget, uint32_t aEventType)
+{
+ AtkObject* wrapper = GetWrapperFor(aTarget);
+
+ switch (aEventType) {
+ case nsIAccessibleEvent::EVENT_FOCUS:
+ atk_focus_tracker_notify(wrapper);
+ atk_object_notify_state_change(wrapper, ATK_STATE_FOCUSED, true);
+ break;
+ case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE:
+ g_signal_emit_by_name(wrapper, "load_complete");
+ break;
+ case nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD:
+ g_signal_emit_by_name(wrapper, "reload");
+ break;
+ case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED:
+ 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:
+ g_object_notify((GObject*)wrapper, "accessible-value");
+ break;
+ case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED:
+ case nsIAccessibleEvent::EVENT_SELECTION_WITHIN:
+ g_signal_emit_by_name(wrapper, "selection_changed");
+ break;
+ }
+}
+
+void
+a11y::ProxyStateChangeEvent(ProxyAccessible* aTarget, uint64_t aState,
+ bool aEnabled)
+{
+ MaiAtkObject* atkObj = MAI_ATK_OBJECT(GetWrapperFor(aTarget));
+ atkObj->FireStateChangeEvent(aState, aEnabled);
+}
+
+void
+a11y::ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset)
+{
+ AtkObject* wrapper = GetWrapperFor(aTarget);
+ g_signal_emit_by_name(wrapper, "text_caret_moved", aOffset);
+}
+
+void
+MaiAtkObject::FireStateChangeEvent(uint64_t aState, bool aEnabled)
+{
+ int32_t stateIndex = AtkStateMap::GetStateIndexFor(aState);
+ if (stateIndex >= 0) {
+ NS_ASSERTION(gAtkStateMap[stateIndex].stateMapEntryType != kNoSuchState,
+ "No such state");
+
+ if (gAtkStateMap[stateIndex].atkState != kNone) {
+ NS_ASSERTION(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::ProxyTextChangeEvent(ProxyAccessible* aTarget, const nsString& 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 nsString& 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::ProxyShowHideEvent(ProxyAccessible* aTarget, ProxyAccessible* aParent,
+ bool aInsert, bool aFromUser)
+{
+ MaiAtkObject* obj = MAI_ATK_OBJECT(GetWrapperFor(aTarget));
+ 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)
+{
+ 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::ProxySelectionEvent(ProxyAccessible*, ProxyAccessible* 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(';');
+ keyBinding = aAccessible->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 000000000..f73d2f0f4
--- /dev/null
+++ b/accessible/atk/AccessibleWrap.h
@@ -0,0 +1,92 @@
+/* -*- 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 "Accessible.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;
+
+/**
+ * AccessibleWrap, and its descendents in atk directory provide the
+ * implementation of AtkObject.
+ */
+class AccessibleWrap : public Accessible
+{
+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;
+ virtual nsresult HandleAccEvent(AccEvent* aEvent) override;
+
+ AtkObject * GetAtkObject(void);
+ static AtkObject* GetAtkObject(Accessible* aAccessible);
+
+ bool IsValidObject();
+
+ static const char * ReturnString(nsAString &aString) {
+ static nsCString returnedString;
+ returnedString = NS_ConvertUTF16toUTF8(aString);
+ 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 000000000..011881023
--- /dev/null
+++ b/accessible/atk/ApplicationAccessibleWrap.cpp
@@ -0,0 +1,167 @@
+/* -*- 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 "nsCOMPtr.h"
+#include "nsMai.h"
+#include "nsAccessibilityService.h"
+
+#include <gtk/gtk.h>
+#include <atk/atk.h>
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+
+// ApplicationAccessibleWrap
+
+ApplicationAccessibleWrap::ApplicationAccessibleWrap():
+ ApplicationAccessible()
+{
+}
+
+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));
+
+ // GTK native dialog
+ if (!IS_MAI_OBJECT(child) &&
+ (atk_object_get_role(child) == ATK_ROLE_DIALOG)) {
+
+ if (data == reinterpret_cast<gpointer>(nsIAccessibleEvent::EVENT_SHOW)) {
+
+ // Attach the dialog accessible to app accessible tree
+ Accessible* windowAcc = GetAccService()->AddNativeRootAccessible(child);
+ g_object_set_qdata(G_OBJECT(child), sQuark_gecko_acc_obj,
+ reinterpret_cast<gpointer>(windowAcc));
+
+ } else {
+
+ // Deattach the dialog accessible
+ Accessible* windowAcc =
+ reinterpret_cast<Accessible*>
+ (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)
+{
+ // 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, 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, Accessible* 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(Accessible* 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 000000000..468616b6c
--- /dev/null
+++ b/accessible/atk/ApplicationAccessibleWrap.h
@@ -0,0 +1,35 @@
+/* -*- 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();
+
+ // Accessible
+ virtual mozilla::a11y::ENameValueFlag Name(nsString& aName) override;
+ virtual bool InsertChildAt(uint32_t aIdx, Accessible* aChild) override;
+ virtual bool RemoveChild(Accessible* 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/AtkSocketAccessible.cpp b/accessible/atk/AtkSocketAccessible.cpp
new file mode 100644
index 000000000..95c3075e8
--- /dev/null
+++ b/accessible/atk/AtkSocketAccessible.cpp
@@ -0,0 +1,126 @@
+/* -*- 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 "AtkSocketAccessible.h"
+
+#include "InterfaceInitFuncs.h"
+#include "nsMai.h"
+#include "mozilla/Likely.h"
+
+using namespace mozilla::a11y;
+
+AtkSocketEmbedType AtkSocketAccessible::g_atk_socket_embed = nullptr;
+GType AtkSocketAccessible::g_atk_socket_type = G_TYPE_INVALID;
+const char* AtkSocketAccessible::sATKSocketEmbedSymbol = "atk_socket_embed";
+const char* AtkSocketAccessible::sATKSocketGetTypeSymbol = "atk_socket_get_type";
+
+bool AtkSocketAccessible::gCanEmbed = FALSE;
+
+extern "C" void mai_atk_component_iface_init(AtkComponentIface* aIface);
+
+G_DEFINE_TYPE_EXTENDED(MaiAtkSocket, mai_atk_socket,
+ AtkSocketAccessible::g_atk_socket_type, 0,
+ G_IMPLEMENT_INTERFACE(ATK_TYPE_COMPONENT,
+ mai_atk_component_iface_init))
+
+void
+mai_atk_socket_class_init(MaiAtkSocketClass* aAcc)
+{
+}
+
+void
+mai_atk_socket_init(MaiAtkSocket* aAcc)
+{
+}
+
+static AtkObject*
+mai_atk_socket_new(AccessibleWrap* aAccWrap)
+{
+ NS_ENSURE_TRUE(aAccWrap, nullptr);
+
+ MaiAtkSocket* acc = nullptr;
+ acc = static_cast<MaiAtkSocket*>(g_object_new(MAI_TYPE_ATK_SOCKET, nullptr));
+ NS_ENSURE_TRUE(acc, nullptr);
+
+ acc->accWrap = aAccWrap;
+ return ATK_OBJECT(acc);
+}
+
+extern "C" {
+static AtkObject*
+RefAccessibleAtPoint(AtkComponent* aComponent, gint aX, gint aY,
+ AtkCoordType aCoordType)
+{
+ NS_ENSURE_TRUE(MAI_IS_ATK_SOCKET(aComponent), nullptr);
+
+ return refAccessibleAtPointHelper(ATK_OBJECT(MAI_ATK_SOCKET(aComponent)),
+ aX, aY, aCoordType);
+}
+
+static void
+GetExtents(AtkComponent* aComponent, gint* aX, gint* aY, gint* aWidth,
+ gint* aHeight, AtkCoordType aCoordType)
+{
+ *aX = *aY = *aWidth = *aHeight = 0;
+
+ if (!MAI_IS_ATK_SOCKET(aComponent))
+ return;
+
+ getExtentsHelper(ATK_OBJECT(MAI_ATK_SOCKET(aComponent)),
+ aX, aY, aWidth, aHeight, aCoordType);
+}
+}
+
+void
+mai_atk_component_iface_init(AtkComponentIface* aIface)
+{
+ NS_ASSERTION(aIface, "Invalid Interface");
+ if (MOZ_UNLIKELY(!aIface))
+ return;
+
+ aIface->ref_accessible_at_point = RefAccessibleAtPoint;
+ aIface->get_extents = GetExtents;
+}
+
+AtkSocketAccessible::AtkSocketAccessible(nsIContent* aContent,
+ DocAccessible* aDoc,
+ const nsCString& aPlugId) :
+ AccessibleWrap(aContent, aDoc)
+{
+ mAtkObject = mai_atk_socket_new(this);
+ if (!mAtkObject)
+ return;
+
+ // Embeds the children of an AtkPlug, specified by plugId, as the children of
+ // this socket.
+ // Using G_TYPE macros instead of ATK_SOCKET macros to avoid undefined
+ // symbols.
+ if (gCanEmbed && G_TYPE_CHECK_INSTANCE_TYPE(mAtkObject, g_atk_socket_type) &&
+ !aPlugId.IsVoid()) {
+ AtkSocket* accSocket =
+ G_TYPE_CHECK_INSTANCE_CAST(mAtkObject, g_atk_socket_type, AtkSocket);
+ g_atk_socket_embed(accSocket, (gchar*)aPlugId.get());
+ }
+}
+
+void
+AtkSocketAccessible::GetNativeInterface(void** aOutAccessible)
+{
+ *aOutAccessible = mAtkObject;
+}
+
+void
+AtkSocketAccessible::Shutdown()
+{
+ if (mAtkObject) {
+ if (MAI_IS_ATK_SOCKET(mAtkObject))
+ MAI_ATK_SOCKET(mAtkObject)->accWrap = nullptr;
+ g_object_unref(mAtkObject);
+ mAtkObject = nullptr;
+ }
+ AccessibleWrap::Shutdown();
+}
diff --git a/accessible/atk/AtkSocketAccessible.h b/accessible/atk/AtkSocketAccessible.h
new file mode 100644
index 000000000..3b63a8e73
--- /dev/null
+++ b/accessible/atk/AtkSocketAccessible.h
@@ -0,0 +1,56 @@
+/* -*- 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 _AtkSocketAccessible_H_
+#define _AtkSocketAccessible_H_
+
+#include "AccessibleWrap.h"
+
+// This file gets included by nsAccessibilityService.cpp, which can't include
+// atk.h (or glib.h), so we can't rely on it being included.
+#ifdef __ATK_H__
+extern "C" typedef void (*AtkSocketEmbedType) (AtkSocket*, gchar*);
+#else
+extern "C" typedef void (*AtkSocketEmbedType) (void*, void*);
+#endif
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Provides a AccessibleWrap wrapper around AtkSocket for out-of-process
+ * accessibles.
+ */
+class AtkSocketAccessible : public AccessibleWrap
+{
+public:
+
+ // Soft references to AtkSocket
+ static AtkSocketEmbedType g_atk_socket_embed;
+#ifdef __ATK_H__
+ static GType g_atk_socket_type;
+#endif
+ static const char* sATKSocketEmbedSymbol;
+ static const char* sATKSocketGetTypeSymbol;
+
+ /*
+ * True if the current Atk version supports AtkSocket and it was correctly
+ * loaded.
+ */
+ static bool gCanEmbed;
+
+ AtkSocketAccessible(nsIContent* aContent, DocAccessible* aDoc,
+ const nsCString& aPlugId);
+
+ virtual void Shutdown() override;
+
+ virtual void GetNativeInterface(void** aOutAccessible) override;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/atk/DocAccessibleWrap.cpp b/accessible/atk/DocAccessibleWrap.cpp
new file mode 100644
index 000000000..d78d724b4
--- /dev/null
+++ b/accessible/atk/DocAccessibleWrap.cpp
@@ -0,0 +1,25 @@
+/* -*- 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 "nsMai.h"
+#include "DocAccessibleWrap.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// DocAccessibleWrap
+////////////////////////////////////////////////////////////////////////////////
+
+DocAccessibleWrap::
+ DocAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell) :
+ DocAccessible(aDocument, aPresShell), mActivated(false)
+{
+}
+
+DocAccessibleWrap::~DocAccessibleWrap()
+{
+}
+
diff --git a/accessible/atk/DocAccessibleWrap.h b/accessible/atk/DocAccessibleWrap.h
new file mode 100644
index 000000000..e5fc8e5e4
--- /dev/null
+++ b/accessible/atk/DocAccessibleWrap.h
@@ -0,0 +1,31 @@
+/* -*- 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 {
+namespace a11y {
+
+class DocAccessibleWrap : public DocAccessible
+{
+public:
+ DocAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell);
+ virtual ~DocAccessibleWrap();
+
+ bool mActivated;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/atk/HTMLTableAccessibleWrap.h b/accessible/atk/HTMLTableAccessibleWrap.h
new file mode 100644
index 000000000..88d40cc2e
--- /dev/null
+++ b/accessible/atk/HTMLTableAccessibleWrap.h
@@ -0,0 +1,23 @@
+/* -*- 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_HTMLTableAccessibleWrap_h__
+#define mozilla_a11y_HTMLTableAccessibleWrap_h__
+
+#include "HTMLTableAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class HTMLTableAccessible HTMLTableAccessibleWrap;
+typedef class HTMLTableCellAccessible HTMLTableCellAccessibleWrap;
+typedef class HTMLTableHeaderCellAccessible HTMLTableHeaderCellAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/atk/HyperTextAccessibleWrap.h b/accessible/atk/HyperTextAccessibleWrap.h
new file mode 100644
index 000000000..a43fe65cf
--- /dev/null
+++ b/accessible/atk/HyperTextAccessibleWrap.h
@@ -0,0 +1,21 @@
+/* -*- 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_HyperTextAccessibleWrap_h__
+#define mozilla_a11y_HyperTextAccessibleWrap_h__
+
+#include "HyperTextAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class HyperTextAccessible HyperTextAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/atk/ImageAccessibleWrap.h b/accessible/atk/ImageAccessibleWrap.h
new file mode 100644
index 000000000..7f1620a59
--- /dev/null
+++ b/accessible/atk/ImageAccessibleWrap.h
@@ -0,0 +1,21 @@
+/* -*- 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_ImageAccessibleWrap_h__
+#define mozilla_a11y_ImageAccessibleWrap_h__
+
+#include "ImageAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class ImageAccessible ImageAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/atk/InterfaceInitFuncs.h b/accessible/atk/InterfaceInitFuncs.h
new file mode 100644
index 000000000..c604a99fc
--- /dev/null
+++ b/accessible/atk/InterfaceInitFuncs.h
@@ -0,0 +1,44 @@
+/* -*- 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 000000000..e64084f5a
--- /dev/null
+++ b/accessible/atk/Platform.cpp
@@ -0,0 +1,377 @@
+/* -*- 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 "nsIGConfService.h"
+#include "nsIServiceManager.h"
+#include "nsMai.h"
+#include "AtkSocketAccessible.h"
+#include "prenv.h"
+#include "prlink.h"
+
+#ifdef MOZ_ENABLE_DBUS
+#include <dbus/dbus.h>
+#endif
+#include <gtk/gtk.h>
+
+#if (MOZ_WIDGET_GTK == 3)
+extern "C" __attribute__((weak,visibility("default"))) int atk_bridge_adaptor_init(int*, char **[]);
+#endif
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+int atkMajorVersion = 1, atkMinorVersion = 12;
+
+GType (*gAtkTableCellGetTypeFunc)();
+
+extern "C" {
+typedef GType (* AtkGetTypeType) (void);
+typedef void (*GnomeAccessibilityInit) (void);
+typedef void (*GnomeAccessibilityShutdown) (void);
+}
+
+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 GnomeAccessibilityModule
+{
+ const char *libName;
+ PRLibrary *lib;
+ const char *initName;
+ GnomeAccessibilityInit init;
+ const char *shutdownName;
+ GnomeAccessibilityShutdown shutdown;
+};
+
+static GnomeAccessibilityModule sAtkBridge = {
+#ifdef AIX
+ "libatk-bridge.a(libatk-bridge.so.0)", nullptr,
+#else
+ "libatk-bridge.so", nullptr,
+#endif
+ "gnome_accessibility_module_init", nullptr,
+ "gnome_accessibility_module_shutdown", nullptr
+};
+
+#if (MOZ_WIDGET_GTK == 2)
+static GnomeAccessibilityModule sGail = {
+ "libgail.so", nullptr,
+ "gnome_accessibility_module_init", nullptr,
+ "gnome_accessibility_module_shutdown", nullptr
+};
+#endif
+
+static nsresult
+LoadGtkModule(GnomeAccessibilityModule& aModule)
+{
+ NS_ENSURE_ARG(aModule.libName);
+
+ if (!(aModule.lib = PR_LoadLibrary(aModule.libName))) {
+ //try to load the module with "gtk-2.0/modules" appended
+ char *curLibPath = PR_GetLibraryPath();
+ nsAutoCString libPath(curLibPath);
+#if defined(LINUX) && defined(__x86_64__)
+ libPath.AppendLiteral(":/usr/lib64:/usr/lib");
+#else
+ libPath.AppendLiteral(":/usr/lib");
+#endif
+ PR_FreeLibraryName(curLibPath);
+
+ int16_t loc1 = 0, loc2 = 0;
+ int16_t subLen = 0;
+ while (loc2 >= 0) {
+ loc2 = libPath.FindChar(':', loc1);
+ if (loc2 < 0)
+ subLen = libPath.Length() - loc1;
+ else
+ subLen = loc2 - loc1;
+ nsAutoCString sub(Substring(libPath, loc1, subLen));
+#if (MOZ_WIDGET_GTK == 2)
+ sub.AppendLiteral("/gtk-2.0/modules/");
+#else
+ sub.AppendLiteral("/gtk-3.0/modules/");
+#endif
+ sub.Append(aModule.libName);
+ aModule.lib = PR_LoadLibrary(sub.get());
+ if (aModule.lib)
+ break;
+
+ loc1 = loc2+1;
+ }
+ if (!aModule.lib)
+ return NS_ERROR_FAILURE;
+ }
+
+ //we have loaded the library, try to get the function ptrs
+ if (!(aModule.init = PR_FindFunctionSymbol(aModule.lib,
+ aModule.initName)) ||
+ !(aModule.shutdown = PR_FindFunctionSymbol(aModule.lib,
+ aModule.shutdownName))) {
+
+ //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();
+
+ AtkGetTypeType pfn_atk_socket_get_type = (AtkGetTypeType)
+ PR_FindFunctionSymbol(sATKLib, AtkSocketAccessible::sATKSocketGetTypeSymbol);
+ if (pfn_atk_socket_get_type) {
+ AtkSocketAccessible::g_atk_socket_type = pfn_atk_socket_get_type();
+ AtkSocketAccessible::g_atk_socket_embed = (AtkSocketEmbedType)
+ PR_FindFunctionSymbol(sATKLib, AtkSocketAccessible ::sATKSocketEmbedSymbol);
+ AtkSocketAccessible::gCanEmbed =
+ AtkSocketAccessible::g_atk_socket_type != G_TYPE_INVALID &&
+ AtkSocketAccessible::g_atk_socket_embed;
+ }
+
+ 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 (*endPtr == '.')
+ atkMinorVersion = strtol(endPtr + 1, &endPtr, 10);
+ }
+ }
+
+#if (MOZ_WIDGET_GTK == 2)
+ // Load and initialize gail library.
+ nsresult rv = LoadGtkModule(sGail);
+ if (NS_SUCCEEDED(rv))
+ (*sGail.init)();
+#endif
+
+ // 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");
+#if (MOZ_WIDGET_GTK == 3)
+ if (atk_bridge_adaptor_init) {
+ atk_bridge_adaptor_init(nullptr, nullptr);
+ } else
+#endif
+ {
+ nsresult rv = LoadGtkModule(sAtkBridge);
+ if (NS_SUCCEEDED(rv)) {
+ (*sAtkBridge.init)();
+ }
+ }
+
+ 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
+ // if (sAtkBridge.shutdown)
+ // (*sAtkBridge.shutdown)();
+ // PR_UnloadLibrary(sAtkBridge.lib);
+ sAtkBridge.lib = nullptr;
+ sAtkBridge.init = nullptr;
+ sAtkBridge.shutdown = nullptr;
+ }
+#if (MOZ_WIDGET_GTK == 2)
+ if (sGail.lib) {
+ // Do not shutdown gail because
+ // 1) Maybe it's not init-ed by us. e.g. GtkEmbed
+ // 2) We need it to avoid assert in spi_atk_tidy_windows
+ // if (sGail.shutdown)
+ // (*sGail.shutdown)();
+ // PR_UnloadLibrary(sGail.lib);
+ sGail.lib = nullptr;
+ sGail.init = nullptr;
+ sGail.shutdown = nullptr;
+ }
+#endif
+ // 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;
+
+ // 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 gconf-2 setting
+#define GCONF_A11Y_KEY "/desktop/gnome/interface/accessibility"
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIGConfService> gconf =
+ do_GetService(NS_GCONFSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && gconf)
+ gconf->GetBool(NS_LITERAL_CSTRING(GCONF_A11Y_KEY), &sShouldEnable);
+
+ return sShouldEnable;
+}
diff --git a/accessible/atk/RootAccessibleWrap.cpp b/accessible/atk/RootAccessibleWrap.cpp
new file mode 100644
index 000000000..e1142e161
--- /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 "nsMai.h"
+
+using namespace mozilla::a11y;
+
+GtkWindowAccessible::GtkWindowAccessible(AtkObject* aAccessible) :
+ DummyAccessible()
+{
+ 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 000000000..cf4b5c18c
--- /dev/null
+++ b/accessible/atk/RootAccessibleWrap.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_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/TextLeafAccessibleWrap.h b/accessible/atk/TextLeafAccessibleWrap.h
new file mode 100644
index 000000000..726feff01
--- /dev/null
+++ b/accessible/atk/TextLeafAccessibleWrap.h
@@ -0,0 +1,20 @@
+/* -*- 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_TextLeafAccessibleWrap_h__
+#define mozilla_a11y_TextLeafAccessibleWrap_h__
+
+#include "TextLeafAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class TextLeafAccessible TextLeafAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/atk/UtilInterface.cpp b/accessible/atk/UtilInterface.cpp
new file mode 100644
index 000000000..a2ab00141
--- /dev/null
+++ b/accessible/atk/UtilInterface.cpp
@@ -0,0 +1,411 @@
+/* -*- 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 "mozilla/Likely.h"
+#include "nsAccessibilityService.h"
+#include "nsMai.h"
+
+#include <atk/atk.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] &&
+ (key->state & GDK_CONTROL_MASK ||
+ 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/XULListboxAccessibleWrap.h b/accessible/atk/XULListboxAccessibleWrap.h
new file mode 100644
index 000000000..c0a759452
--- /dev/null
+++ b/accessible/atk/XULListboxAccessibleWrap.h
@@ -0,0 +1,21 @@
+/* -*- 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_XULListboxAccessibleWrap_h__
+#define mozilla_a11y_XULListboxAccessibleWrap_h__
+
+#include "XULListboxAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class XULListboxAccessible XULListboxAccessibleWrap;
+typedef class XULListCellAccessible XULListCellAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/atk/XULMenuAccessibleWrap.h b/accessible/atk/XULMenuAccessibleWrap.h
new file mode 100644
index 000000000..d9e325053
--- /dev/null
+++ b/accessible/atk/XULMenuAccessibleWrap.h
@@ -0,0 +1,20 @@
+/* -*- 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_XULMenuAccessibleWrap_h__
+#define mozilla_a11y_XULMenuAccessibleWrap_h__
+
+#include "XULMenuAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class XULMenuitemAccessible XULMenuitemAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/atk/XULTreeGridAccessibleWrap.h b/accessible/atk/XULTreeGridAccessibleWrap.h
new file mode 100644
index 000000000..b7f23434b
--- /dev/null
+++ b/accessible/atk/XULTreeGridAccessibleWrap.h
@@ -0,0 +1,22 @@
+/* -*- 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_XULTreeGridAccessibleWrap_h__
+#define mozilla_a11y_XULTreeGridAccessibleWrap_h__
+
+#include "XULTreeGridAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class XULTreeGridAccessible XULTreeGridAccessibleWrap;
+typedef class XULTreeGridCellAccessible XULTreeGridCellAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/atk/moz.build b/accessible/atk/moz.build
new file mode 100644
index 000000000..0d6ff9bbe
--- /dev/null
+++ b/accessible/atk/moz.build
@@ -0,0 +1,63 @@
+# -*- 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',
+ 'HyperTextAccessibleWrap.h',
+]
+
+SOURCES += [
+ 'AccessibleWrap.cpp',
+ 'ApplicationAccessibleWrap.cpp',
+ 'AtkSocketAccessible.cpp',
+ 'DocAccessibleWrap.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/ipc/other',
+ '/accessible/xpcom',
+ '/accessible/xul',
+ '/other-licenses/atk-1.0',
+]
+
+FINAL_LIBRARY = 'xul'
+
+if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ CFLAGS += CONFIG['TK_CFLAGS']
+ CXXFLAGS += CONFIG['TK_CFLAGS']
+
+if CONFIG['MOZ_ENABLE_DBUS']:
+ CXXFLAGS += CONFIG['MOZ_DBUS_CFLAGS']
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+if CONFIG['CLANG_CXX'] or CONFIG['GNU_CXX']:
+ # Used in G_DEFINE_TYPE_EXTENDED macro, probably fixed in newer glib /
+ # gobject headers. See bug 1243331 comment 3.
+ CXXFLAGS += [
+ '-Wno-error=shadow',
+ '-Wno-unused-local-typedefs',
+ ]
diff --git a/accessible/atk/nsMai.h b/accessible/atk/nsMai.h
new file mode 100644
index 000000000..1a9082e99
--- /dev/null
+++ b/accessible/atk/nsMai.h
@@ -0,0 +1,149 @@
+/* -*- 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 "AccessibleOrProxy.h"
+#include "AccessibleWrap.h"
+
+namespace mozilla {
+namespace a11y {
+class ProxyAccessible;
+}
+}
+
+#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();
+extern "C" GType mai_atk_socket_get_type(void);
+
+/* MaiAtkSocket */
+
+#define MAI_TYPE_ATK_SOCKET (mai_atk_socket_get_type ())
+#define MAI_ATK_SOCKET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\
+ MAI_TYPE_ATK_SOCKET, MaiAtkSocket))
+#define MAI_IS_ATK_SOCKET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\
+ MAI_TYPE_ATK_SOCKET))
+#define MAI_ATK_SOCKET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\
+ MAI_TYPE_ATK_SOCKET,\
+ MaiAtkSocketClass))
+#define MAI_IS_ATK_SOCKET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\
+ MAI_TYPE_ATK_SOCKET))
+#define MAI_ATK_SOCKET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\
+ MAI_TYPE_ATK_SOCKET,\
+ MaiAtkSocketClass))
+
+typedef struct _MaiAtkSocket
+{
+ AtkSocket parent;
+
+ mozilla::a11y::AccessibleWrap* accWrap;
+} MaiAtkSocket;
+
+typedef struct _MaiAtkSocketClass
+{
+ AtkSocketClass parent_class;
+} MaiAtkSocketClass;
+
+// 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::ProxyAccessible* GetProxy(AtkObject* aAtkObj);
+mozilla::a11y::AccessibleOrProxy GetInternalObj(AtkObject* aObj);
+AtkObject* GetWrapperFor(mozilla::a11y::ProxyAccessible* aProxy);
+AtkObject* GetWrapperFor(mozilla::a11y::AccessibleOrProxy aObj);
+
+extern int atkMajorVersion, atkMinorVersion;
+
+/**
+ * Return true if the loaded version of libatk-1.0.so is at least
+ * aMajor.aMinor.0.
+ */
+static inline bool
+IsAtkVersionAtLeast(int aMajor, int aMinor)
+{
+ return aMajor < atkMajorVersion ||
+ (aMajor == atkMajorVersion && aMinor <= atkMinorVersion);
+}
+
+// This is or'd with the pointer in MaiAtkObject::accWrap if the wrap-ee is a
+// proxy.
+static const uintptr_t IS_PROXY = 1;
+
+/**
+ * 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::AccessibleOrProxy accWrap;
+
+ /*
+ * 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 nsString& 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 000000000..9d33f6665
--- /dev/null
+++ b/accessible/atk/nsMaiHyperlink.cpp
@@ -0,0 +1,262 @@
+/* -*- 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/ProxyAccessible.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(AccessibleOrProxy 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;
+
+ nsAutoCString cautoStr;
+ if (Accessible* hyperlink = maiLink->GetAccHyperlink()) {
+ nsCOMPtr<nsIURI> uri = hyperlink->AnchorURIAt(aLinkIndex);
+ if (!uri)
+ return nullptr;
+
+ nsresult rv = uri->GetSpec(cautoStr);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ return g_strdup(cautoStr.get());
+ }
+
+ bool valid;
+ maiLink->Proxy()->AnchorURIAt(aLinkIndex, cautoStr, &valid);
+ if (!valid)
+ return nullptr;
+
+ return g_strdup(cautoStr.get());
+}
+
+AtkObject *
+getObjectCB(AtkHyperlink *aLink, gint aLinkIndex)
+{
+ MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
+ if (!maiLink) {
+ return nullptr;
+ }
+
+ if (Accessible* hyperlink = maiLink->GetAccHyperlink()) {
+ Accessible* anchor = hyperlink->AnchorAt(aLinkIndex);
+ NS_ENSURE_TRUE(anchor, nullptr);
+
+ return AccessibleWrap::GetAtkObject(anchor);
+ }
+
+ ProxyAccessible* anchor = maiLink->Proxy()->AnchorAt(aLinkIndex);
+ return anchor ? GetWrapperFor(anchor) : nullptr;
+}
+
+gint
+getEndIndexCB(AtkHyperlink *aLink)
+{
+ MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
+ if (!maiLink)
+ return false;
+
+ if (Accessible* hyperlink = maiLink->GetAccHyperlink())
+ return static_cast<gint>(hyperlink->EndOffset());
+
+ bool valid = false;
+ uint32_t endIdx = maiLink->Proxy()->EndOffset(&valid);
+ return valid ? static_cast<gint>(endIdx) : -1;
+}
+
+gint
+getStartIndexCB(AtkHyperlink *aLink)
+{
+ MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
+ if (!maiLink)
+ return -1;
+
+ if (Accessible* hyperlink = maiLink->GetAccHyperlink())
+ return static_cast<gint>(hyperlink->StartOffset());
+
+ bool valid = false;
+ uint32_t startIdx = maiLink->Proxy()->StartOffset(&valid);
+ return valid ? static_cast<gint>(startIdx) : -1;
+}
+
+gboolean
+isValidCB(AtkHyperlink *aLink)
+{
+ MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
+ if (!maiLink)
+ return false;
+
+ if (Accessible* hyperlink = maiLink->GetAccHyperlink())
+ return static_cast<gboolean>(hyperlink->IsLinkValid());
+
+ return static_cast<gboolean>(maiLink->Proxy()->IsLinkValid());
+}
+
+gint
+getAnchorCountCB(AtkHyperlink *aLink)
+{
+ MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
+ if (!maiLink)
+ return -1;
+
+ if (Accessible* hyperlink = maiLink->GetAccHyperlink())
+ return static_cast<gint>(hyperlink->AnchorCount());
+
+ bool valid = false;
+ uint32_t anchorCount = maiLink->Proxy()->AnchorCount(&valid);
+ return valid ? static_cast<gint>(anchorCount) : -1;
+}
diff --git a/accessible/atk/nsMaiHyperlink.h b/accessible/atk/nsMaiHyperlink.h
new file mode 100644
index 000000000..7dc1b7355
--- /dev/null
+++ b/accessible/atk/nsMaiHyperlink.h
@@ -0,0 +1,55 @@
+/* -*- 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 "Accessible.h"
+
+struct _AtkHyperlink;
+typedef struct _AtkHyperlink AtkHyperlink;
+
+namespace mozilla {
+namespace a11y {
+
+/*
+ * MaiHyperlink is a auxiliary class for MaiInterfaceHyperText.
+ */
+
+class MaiHyperlink
+{
+public:
+ explicit MaiHyperlink(AccessibleOrProxy aHyperLink);
+ ~MaiHyperlink();
+
+public:
+ AtkHyperlink* GetAtkHyperlink() const { return mMaiAtkHyperlink; }
+ Accessible* GetAccHyperlink()
+ {
+ if (!mHyperlink.IsAccessible())
+ return nullptr;
+
+ Accessible* link = mHyperlink.AsAccessible();
+ if (!link) {
+ return nullptr;
+ }
+
+ NS_ASSERTION(link->IsLink(), "Why isn't it a link!");
+ return link;
+ }
+
+ ProxyAccessible* Proxy() const { return mHyperlink.AsProxy(); }
+
+protected:
+ AccessibleOrProxy 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 000000000..9ba121665
--- /dev/null
+++ b/accessible/atk/nsMaiInterfaceAction.cpp
@@ -0,0 +1,105 @@
+/* -*- 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 "Accessible-inl.h"
+#include "nsMai.h"
+#include "Role.h"
+#include "mozilla/Likely.h"
+#include "ProxyAccessible.h"
+#include "nsString.h"
+
+using namespace mozilla::a11y;
+
+extern "C" {
+
+static gboolean
+doActionCB(AtkAction *aAction, gint aActionIndex)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aAction));
+ if (accWrap) {
+ return accWrap->DoAction(aActionIndex);
+ }
+
+ ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aAction));
+ return proxy && proxy->DoAction(aActionIndex);
+}
+
+static gint
+getActionCountCB(AtkAction *aAction)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aAction));
+ if (accWrap) {
+ return accWrap->ActionCount();
+ }
+
+ ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aAction));
+ return proxy ? proxy->ActionCount() : 0;
+}
+
+static const gchar*
+getActionDescriptionCB(AtkAction *aAction, gint aActionIndex)
+{
+ nsAutoString description;
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aAction));
+ if (accWrap) {
+ accWrap->ActionDescriptionAt(aActionIndex, description);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aAction))) {
+ proxy->ActionDescriptionAt(aActionIndex, description);
+ } else {
+ return nullptr;
+ }
+
+ return AccessibleWrap::ReturnString(description);
+}
+
+static const gchar*
+getActionNameCB(AtkAction *aAction, gint aActionIndex)
+{
+ nsAutoString autoStr;
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aAction));
+ if (accWrap) {
+ accWrap->ActionNameAt(aActionIndex, autoStr);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aAction))) {
+ proxy->ActionNameAt(aActionIndex, autoStr);
+ } else {
+ return nullptr;
+ }
+
+ return AccessibleWrap::ReturnString(autoStr);
+}
+
+static const gchar*
+getKeyBindingCB(AtkAction *aAction, gint aActionIndex)
+{
+ nsAutoString keyBindingsStr;
+ AccessibleWrap* acc = GetAccessibleWrap(ATK_OBJECT(aAction));
+ if (acc) {
+ AccessibleWrap::GetKeyBinding(acc, keyBindingsStr);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aAction))) {
+ proxy->AtkKeyBinding(keyBindingsStr);
+ } else {
+ return nullptr;
+ }
+
+ 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 000000000..efd8eb65c
--- /dev/null
+++ b/accessible/atk/nsMaiInterfaceComponent.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 "InterfaceInitFuncs.h"
+
+#include "AccessibleWrap.h"
+#include "nsAccUtils.h"
+#include "nsCoreUtils.h"
+#include "nsMai.h"
+#include "mozilla/Likely.h"
+#include "mozilla/a11y/ProxyAccessible.h"
+
+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);
+ AccessibleWrap* accWrap = GetAccessibleWrap(atkObject);
+ if (accWrap) {
+ accWrap->TakeFocus();
+ return TRUE;
+ }
+
+ ProxyAccessible* proxy = GetProxy(atkObject);
+ if (proxy) {
+ proxy->TakeFocus();
+ return TRUE;
+ }
+
+ return FALSE;
+}
+}
+
+AtkObject*
+refAccessibleAtPointHelper(AtkObject* aAtkObj, gint aX, gint aY,
+ AtkCoordType aCoordType)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj);
+ if (accWrap) {
+ if (accWrap->IsDefunct() || nsAccUtils::MustPrune(accWrap)) {
+ return nullptr;
+ }
+
+ // Accessible::ChildAtPoint(x,y) is in screen pixels.
+ if (aCoordType == ATK_XY_WINDOW) {
+ nsIntPoint winCoords =
+ nsCoreUtils::GetScreenCoordsForWindow(accWrap->GetNode());
+ aX += winCoords.x;
+ aY += winCoords.y;
+ }
+
+ Accessible* accAtPoint = accWrap->ChildAtPoint(aX, aY,
+ Accessible::eDirectChild);
+ if (!accAtPoint) {
+ return nullptr;
+ }
+
+ AtkObject* atkObj = AccessibleWrap::GetAtkObject(accAtPoint);
+ if (atkObj) {
+ g_object_ref(atkObj);
+ }
+
+ return atkObj;
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(aAtkObj)) {
+ ProxyAccessible* result =
+ proxy->AccessibleAtPoint(aX, aY, aCoordType == ATK_XY_WINDOW);
+ AtkObject* atkObj = result ? GetWrapperFor(result) : nullptr;
+ if (atkObj) {
+ g_object_ref(atkObj);
+ }
+ return atkObj;
+ }
+
+ return nullptr;
+}
+
+void
+getExtentsHelper(AtkObject* aAtkObj,
+ gint* aX, gint* aY, gint* aWidth, gint* aHeight,
+ AtkCoordType aCoordType)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj);
+ *aX = *aY = *aWidth = *aHeight = 0;
+
+ if (accWrap) {
+ if (accWrap->IsDefunct()) {
+ return;
+ }
+
+ nsIntRect screenRect = accWrap->Bounds();
+ if (screenRect.IsEmpty())
+ return;
+
+ if (aCoordType == ATK_XY_WINDOW) {
+ nsIntPoint winCoords =
+ nsCoreUtils::GetScreenCoordsForWindow(accWrap->GetNode());
+ screenRect.x -= winCoords.x;
+ screenRect.y -= winCoords.y;
+ }
+
+ *aX = screenRect.x;
+ *aY = screenRect.y;
+ *aWidth = screenRect.width;
+ *aHeight = screenRect.height;
+ return;
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(aAtkObj)) {
+ proxy->Extents(aCoordType == ATK_XY_WINDOW, aX, aY, aWidth, aHeight);
+ }
+}
+
+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;
+}
diff --git a/accessible/atk/nsMaiInterfaceDocument.cpp b/accessible/atk/nsMaiInterfaceDocument.cpp
new file mode 100644
index 000000000..801028f9c
--- /dev/null
+++ b/accessible/atk/nsMaiInterfaceDocument.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 "InterfaceInitFuncs.h"
+
+#include "Accessible-inl.h"
+#include "AccessibleWrap.h"
+#include "DocAccessible.h"
+#include "nsMai.h"
+#include "ProxyAccessible.h"
+#include "mozilla/Likely.h"
+
+using namespace mozilla::a11y;
+
+static const char* const kDocTypeName = "W3C-doctype";
+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.
+ * get_document_type is deprecated, we return DocType in
+ * get_document_attribute_value and get_document_attributes instead.
+ */
+ aIface->get_document_attributes = getDocumentAttributesCB;
+ aIface->get_document_attribute_value = getDocumentAttributeValueCB;
+ aIface->get_document_locale = getDocumentLocaleCB;
+}
+
+const gchar *
+getDocumentLocaleCB(AtkDocument *aDocument)
+{
+ nsAutoString locale;
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aDocument));
+ if (accWrap) {
+ accWrap->Language(locale);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aDocument))) {
+ proxy->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 w3cDocType;
+ nsAutoString mimeType;
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aDocument));
+ if (accWrap) {
+ if (!accWrap->IsDoc()) {
+ return nullptr;
+ }
+
+ DocAccessible* document = accWrap->AsDoc();
+ document->URL(url);
+ document->DocType(w3cDocType);
+ document->MimeType(mimeType);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aDocument))) {
+ proxy->URLDocTypeMimeType(url, w3cDocType, mimeType);
+ } else {
+ return nullptr;
+ }
+
+ // according to atkobject.h, AtkAttributeSet is a GSList
+ GSList* attributes = nullptr;
+ attributes = prependToList(attributes, kDocUrlName, url);
+ attributes = prependToList(attributes, kDocTypeName, w3cDocType);
+ attributes = prependToList(attributes, kMimeTypeName, mimeType);
+
+ return attributes;
+}
+
+const gchar *
+getDocumentAttributeValueCB(AtkDocument *aDocument,
+ const gchar *aAttrName)
+{
+ ProxyAccessible* proxy = nullptr;
+ DocAccessible* document = nullptr;
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aDocument));
+ if (accWrap) {
+ if (!accWrap->IsDoc()) {
+ return nullptr;
+ }
+
+ document = accWrap->AsDoc();
+ } else {
+ proxy = GetProxy(ATK_OBJECT(aDocument));
+ if (!proxy) {
+ return nullptr;
+ }
+ }
+
+ nsAutoString attrValue;
+ if (!strcasecmp(aAttrName, kDocTypeName)) {
+ if (document) {
+ document->DocType(attrValue);
+ } else {
+ proxy->DocType(attrValue);
+ }
+ } else if (!strcasecmp(aAttrName, kDocUrlName)) {
+ if (document) {
+ document->URL(attrValue);
+ } else {
+ proxy->URL(attrValue);
+ }
+ } else if (!strcasecmp(aAttrName, kMimeTypeName)) {
+ if (document) {
+ document->MimeType(attrValue);
+ } else {
+ proxy->MimeType(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 000000000..18a1b6f42
--- /dev/null
+++ b/accessible/atk/nsMaiInterfaceEditableText.cpp
@@ -0,0 +1,134 @@
+/* -*- 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 "Accessible-inl.h"
+#include "HyperTextAccessible-inl.h"
+#include "nsMai.h"
+#include "ProxyAccessible.h"
+#include "nsString.h"
+#include "mozilla/Likely.h"
+
+using namespace mozilla::a11y;
+
+extern "C" {
+static void
+setTextContentsCB(AtkEditableText *aText, const gchar *aString)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ if (accWrap) {
+ HyperTextAccessible* text = accWrap->AsHyperText();
+ if (!text || !text->IsTextRole()) {
+ return;
+ }
+
+ NS_ConvertUTF8toUTF16 strContent(aString);
+ text->ReplaceText(strContent);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+ NS_ConvertUTF8toUTF16 strContent(aString);
+ proxy->ReplaceText(strContent);
+ }
+}
+
+static void
+insertTextCB(AtkEditableText *aText,
+ const gchar *aString, gint aLength, gint *aPosition)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ if (accWrap) {
+ HyperTextAccessible* text = accWrap->AsHyperText();
+ if (!text || !text->IsTextRole()) {
+ return;
+ }
+
+ NS_ConvertUTF8toUTF16 strContent(aString);
+ text->InsertText(strContent, *aPosition);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+ NS_ConvertUTF8toUTF16 strContent(aString);
+ proxy->InsertText(strContent, *aPosition);
+ }
+}
+
+static void
+copyTextCB(AtkEditableText *aText, gint aStartPos, gint aEndPos)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ if (accWrap) {
+ HyperTextAccessible* text = accWrap->AsHyperText();
+ if (!text || !text->IsTextRole()) {
+ return;
+ }
+
+ text->CopyText(aStartPos, aEndPos);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+ proxy->CopyText(aStartPos, aEndPos);
+ }
+}
+
+static void
+cutTextCB(AtkEditableText *aText, gint aStartPos, gint aEndPos)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ if (accWrap) {
+ HyperTextAccessible* text = accWrap->AsHyperText();
+ if (!text || !text->IsTextRole()) {
+ return;
+ }
+
+ text->CutText(aStartPos, aEndPos);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+ proxy->CutText(aStartPos, aEndPos);
+ }
+}
+
+static void
+deleteTextCB(AtkEditableText *aText, gint aStartPos, gint aEndPos)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ if (accWrap) {
+ HyperTextAccessible* text = accWrap->AsHyperText();
+ if (!text || !text->IsTextRole()) {
+ return;
+ }
+
+ text->DeleteText(aStartPos, aEndPos);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+ proxy->DeleteText(aStartPos, aEndPos);
+ }
+}
+
+static void
+pasteTextCB(AtkEditableText *aText, gint aPosition)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ if (accWrap) {
+ HyperTextAccessible* text = accWrap->AsHyperText();
+ if (!text || !text->IsTextRole()) {
+ return;
+ }
+
+ text->PasteText(aPosition);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+ proxy->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 000000000..f8cfd6fc1
--- /dev/null
+++ b/accessible/atk/nsMaiInterfaceHyperlinkImpl.cpp
@@ -0,0 +1,37 @@
+/* -*- 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)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aImpl));
+ if (!accWrap && !GetProxy(ATK_OBJECT(aImpl)))
+ return nullptr;
+
+ if (accWrap)
+ NS_ASSERTION(accWrap->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 000000000..d6b3ee8f4
--- /dev/null
+++ b/accessible/atk/nsMaiInterfaceHypertext.cpp
@@ -0,0 +1,94 @@
+/* -*- 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 "Accessible-inl.h"
+#include "HyperTextAccessible.h"
+#include "nsMai.h"
+#include "nsMaiHyperlink.h"
+#include "ProxyAccessible.h"
+#include "mozilla/Likely.h"
+
+
+using namespace mozilla::a11y;
+
+extern "C" {
+
+static AtkHyperlink*
+getLinkCB(AtkHypertext *aText, gint aLinkIndex)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ AtkObject* atkHyperLink = nullptr;
+ if (accWrap) {
+ HyperTextAccessible* hyperText = accWrap->AsHyperText();
+ NS_ENSURE_TRUE(hyperText, nullptr);
+
+ Accessible* hyperLink = hyperText->LinkAt(aLinkIndex);
+ if (!hyperLink || !hyperLink->IsLink()) {
+ return nullptr;
+ }
+
+ atkHyperLink = AccessibleWrap::GetAtkObject(hyperLink);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+ ProxyAccessible* proxyLink = proxy->LinkAt(aLinkIndex);
+ if (!proxyLink)
+ return nullptr;
+
+ atkHyperLink = GetWrapperFor(proxyLink);
+ }
+
+ NS_ENSURE_TRUE(IS_MAI_OBJECT(atkHyperLink), nullptr);
+ return MAI_ATK_OBJECT(atkHyperLink)->GetAtkHyperlink();
+}
+
+static gint
+getLinkCountCB(AtkHypertext *aText)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ if (accWrap) {
+ HyperTextAccessible* hyperText = accWrap->AsHyperText();
+ NS_ENSURE_TRUE(hyperText, -1);
+ return hyperText->LinkCount();
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+ return proxy->LinkCount();
+ }
+
+ return -1;
+}
+
+static gint
+getLinkIndexCB(AtkHypertext *aText, gint aCharIndex)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ if (accWrap) {
+ HyperTextAccessible* hyperText = accWrap->AsHyperText();
+ NS_ENSURE_TRUE(hyperText, -1);
+
+ return hyperText->LinkIndexAtOffset(aCharIndex);
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+ return proxy->LinkIndexAtOffset(aCharIndex);
+ }
+
+ return -1;
+}
+}
+
+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 000000000..64a3beaab
--- /dev/null
+++ b/accessible/atk/nsMaiInterfaceImage.cpp
@@ -0,0 +1,77 @@
+/* -*- 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 "ImageAccessible.h"
+#include "mozilla/Likely.h"
+#include "nsMai.h"
+#include "nsIAccessibleTypes.h"
+#include "nsIURI.h"
+#include "ProxyAccessible.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)
+{
+ nsIntPoint pos;
+ uint32_t geckoCoordType = (aCoordType == ATK_XY_WINDOW) ?
+ nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE :
+ nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE;
+
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aImage));
+ if (accWrap && accWrap->IsImage()) {
+ ImageAccessible* image = accWrap->AsImage();
+ pos = image->Position(geckoCoordType);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aImage))) {
+ pos = proxy->ImagePosition(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)
+{
+ nsIntSize size;
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aImage));
+ if (accWrap && accWrap->IsImage()) {
+ size = accWrap->AsImage()->Size();
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aImage))) {
+ size = proxy->ImageSize();
+ }
+
+ *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 000000000..2ce63a451
--- /dev/null
+++ b/accessible/atk/nsMaiInterfaceSelection.cpp
@@ -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 "InterfaceInitFuncs.h"
+
+#include "Accessible-inl.h"
+#include "AccessibleWrap.h"
+#include "nsMai.h"
+#include "ProxyAccessible.h"
+#include "mozilla/Likely.h"
+
+#include <atk/atk.h>
+
+using namespace mozilla::a11y;
+
+extern "C" {
+
+static gboolean
+addSelectionCB(AtkSelection *aSelection, gint i)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aSelection));
+ if (accWrap && accWrap->IsSelect()) {
+ return accWrap->AddItemToSelection(i);
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aSelection))) {
+ return proxy->AddItemToSelection(i);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+clearSelectionCB(AtkSelection *aSelection)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aSelection));
+ if (accWrap && accWrap->IsSelect()) {
+ return accWrap->UnselectAll();
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aSelection))) {
+ return proxy->UnselectAll();
+ }
+
+ return FALSE;
+}
+
+static AtkObject*
+refSelectionCB(AtkSelection *aSelection, gint i)
+{
+ AtkObject* atkObj = nullptr;
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aSelection));
+ if (accWrap && accWrap->IsSelect()) {
+ Accessible* selectedItem = accWrap->GetSelectedItem(i);
+ if (!selectedItem) {
+ return nullptr;
+ }
+
+ atkObj = AccessibleWrap::GetAtkObject(selectedItem);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aSelection))) {
+ ProxyAccessible* selectedItem = proxy->GetSelectedItem(i);
+ if (selectedItem) {
+ atkObj = GetWrapperFor(selectedItem);
+ }
+ }
+
+ if (atkObj) {
+ g_object_ref(atkObj);
+ }
+
+ return atkObj;
+}
+
+static gint
+getSelectionCountCB(AtkSelection *aSelection)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aSelection));
+ if (accWrap && accWrap->IsSelect()) {
+ return accWrap->SelectedItemCount();
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aSelection))) {
+ return proxy->SelectedItemCount();
+ }
+
+ return -1;
+}
+
+static gboolean
+isChildSelectedCB(AtkSelection *aSelection, gint i)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aSelection));
+ if (accWrap && accWrap->IsSelect()) {
+ return accWrap->IsItemSelected(i);
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aSelection))) {
+ return proxy->IsItemSelected(i);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+removeSelectionCB(AtkSelection *aSelection, gint i)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aSelection));
+ if (accWrap && accWrap->IsSelect()) {
+ return accWrap->RemoveItemFromSelection(i);
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aSelection))) {
+ return proxy->RemoveItemFromSelection(i);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+selectAllSelectionCB(AtkSelection *aSelection)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aSelection));
+ if (accWrap && accWrap->IsSelect()) {
+ return accWrap->SelectAll();
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aSelection))) {
+ return proxy->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 000000000..c94815f3c
--- /dev/null
+++ b/accessible/atk/nsMaiInterfaceTable.cpp
@@ -0,0 +1,391 @@
+/* -*- 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 "Accessible-inl.h"
+#include "AccessibleWrap.h"
+#include "nsAccUtils.h"
+#include "TableAccessible.h"
+#include "TableCellAccessible.h"
+#include "nsMai.h"
+#include "ProxyAccessible.h"
+#include "nsArrayUtils.h"
+
+#include "mozilla/Likely.h"
+
+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;
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
+ if (accWrap) {
+ Accessible* cell = accWrap->AsTable()->CellAt(aRowIdx, aColIdx);
+ if (!cell) {
+ return nullptr;
+ }
+
+ cellAtkObj = AccessibleWrap::GetAtkObject(cell);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
+ ProxyAccessible* cell = proxy->TableCellAt(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;
+ }
+
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
+ if (accWrap) {
+ return static_cast<gint>(accWrap->AsTable()->CellIndexAt(aRowIdx, aColIdx));
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
+ return static_cast<gint>(proxy->TableCellIndexAt(aRowIdx, aColIdx));
+ }
+
+ return -1;
+}
+
+static gint
+getColumnAtIndexCB(AtkTable *aTable, gint aIdx)
+{
+ if (aIdx < 0) {
+ return -1;
+ }
+
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
+ if (accWrap) {
+ return static_cast<gint>(accWrap->AsTable()->ColIndexAt(aIdx));
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
+ return static_cast<gint>(proxy->TableColumnIndexAt(aIdx));
+ }
+
+ return -1;
+}
+
+static gint
+getRowAtIndexCB(AtkTable *aTable, gint aIdx)
+{
+ if (aIdx < 0) {
+ return -1;
+ }
+
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
+ if (accWrap) {
+ return static_cast<gint>(accWrap->AsTable()->RowIndexAt(aIdx));
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
+ return static_cast<gint>(proxy->TableRowIndexAt(aIdx));
+ }
+
+ return -1;
+}
+
+static gint
+getColumnCountCB(AtkTable *aTable)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
+ if (accWrap) {
+ return static_cast<gint>(accWrap->AsTable()->ColCount());
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
+ return static_cast<gint>(proxy->TableColumnCount());
+ }
+
+ return -1;
+}
+
+static gint
+getRowCountCB(AtkTable *aTable)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
+ if (accWrap) {
+ return static_cast<gint>(accWrap->AsTable()->RowCount());
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
+ return static_cast<gint>(proxy->TableRowCount());
+ }
+
+ return -1;
+}
+
+static gint
+getColumnExtentAtCB(AtkTable *aTable, gint aRowIdx, gint aColIdx)
+{
+ if (aRowIdx < 0 || aColIdx < 0) {
+ return -1;
+ }
+
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
+ if (accWrap) {
+ return static_cast<gint>(accWrap->AsTable()->ColExtentAt(aRowIdx, aColIdx));
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
+ return static_cast<gint>(proxy->TableColumnExtentAt(aRowIdx, aColIdx));
+ }
+
+ return -1;
+}
+
+static gint
+getRowExtentAtCB(AtkTable *aTable, gint aRowIdx, gint aColIdx)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
+ if (accWrap) {
+ return static_cast<gint>(accWrap->AsTable()->RowExtentAt(aRowIdx, aColIdx));
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
+ return static_cast<gint>(proxy->TableRowExtentAt(aRowIdx, aColIdx));
+ }
+
+ return -1;
+}
+
+static AtkObject*
+getCaptionCB(AtkTable* aTable)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
+ if (accWrap) {
+ Accessible* caption = accWrap->AsTable()->Caption();
+ return caption ? AccessibleWrap::GetAtkObject(caption) : nullptr;
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
+ ProxyAccessible* caption = proxy->TableCaption();
+ return caption ? GetWrapperFor(caption) : nullptr;
+ }
+
+ return nullptr;
+}
+
+static const gchar*
+getColumnDescriptionCB(AtkTable *aTable, gint aColumn)
+{
+ nsAutoString autoStr;
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
+ if (accWrap) {
+ accWrap->AsTable()->ColDescription(aColumn, autoStr);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
+ proxy->TableColumnDescription(aColumn, autoStr);
+ } else {
+ return nullptr;
+ }
+
+ return AccessibleWrap::ReturnString(autoStr);
+}
+
+static AtkObject*
+getColumnHeaderCB(AtkTable *aTable, gint aColIdx)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
+ if (accWrap) {
+ Accessible* header =
+ AccessibleWrap::GetColumnHeader(accWrap->AsTable(), aColIdx);
+ return header ? AccessibleWrap::GetAtkObject(header) : nullptr;
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
+ ProxyAccessible* header = proxy->AtkTableColumnHeader(aColIdx);
+ return header ? GetWrapperFor(header) : nullptr;
+ }
+
+ return nullptr;
+}
+
+static const gchar*
+getRowDescriptionCB(AtkTable *aTable, gint aRow)
+{
+ nsAutoString autoStr;
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
+ if (accWrap) {
+ accWrap->AsTable()->RowDescription(aRow, autoStr);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
+ proxy->TableRowDescription(aRow, autoStr);
+ } else {
+ return nullptr;
+ }
+
+ return AccessibleWrap::ReturnString(autoStr);
+}
+
+static AtkObject*
+getRowHeaderCB(AtkTable *aTable, gint aRowIdx)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
+ if (accWrap) {
+ Accessible* header =
+ AccessibleWrap::GetRowHeader(accWrap->AsTable(), aRowIdx);
+ return header ? AccessibleWrap::GetAtkObject(header) : nullptr;
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
+ ProxyAccessible* header = proxy->AtkTableRowHeader(aRowIdx);
+ return header ? GetWrapperFor(header) : nullptr;
+ }
+
+ return 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;
+
+ AutoTArray<uint32_t, 10> cols;
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
+ if (accWrap) {
+ accWrap->AsTable()->SelectedColIndices(&cols);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
+ proxy->TableSelectedColumnIndices(&cols);
+ } else {
+ return 0;
+ }
+
+ 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)
+{
+ AutoTArray<uint32_t, 10> rows;
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
+ if (accWrap) {
+ accWrap->AsTable()->SelectedRowIndices(&rows);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
+ proxy->TableSelectedRowIndices(&rows);
+ } else {
+ return 0;
+ }
+
+ 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)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
+ if (accWrap) {
+ return static_cast<gboolean>(accWrap->AsTable()->IsColSelected(aColIdx));
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
+ return static_cast<gboolean>(proxy->TableColumnSelected(aColIdx));
+ }
+
+ return FALSE;
+}
+
+static gboolean
+isRowSelectedCB(AtkTable *aTable, gint aRowIdx)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
+ if (accWrap) {
+ return static_cast<gboolean>(accWrap->AsTable()->IsRowSelected(aRowIdx));
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
+ return static_cast<gboolean>(proxy->TableRowSelected(aRowIdx));
+ }
+
+ return FALSE;
+}
+
+static gboolean
+isCellSelectedCB(AtkTable *aTable, gint aRowIdx, gint aColIdx)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
+ if (accWrap) {
+ return static_cast<gboolean>(accWrap->AsTable()->
+ IsCellSelected(aRowIdx, aColIdx));
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
+ return static_cast<gboolean>(proxy->TableCellSelected(aRowIdx, aColIdx));
+ }
+
+ return FALSE;
+}
+}
+
+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 000000000..39bdd4067
--- /dev/null
+++ b/accessible/atk/nsMaiInterfaceTableCell.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 "InterfaceInitFuncs.h"
+
+#include "Accessible-inl.h"
+#include "AccessibleWrap.h"
+#include "nsAccUtils.h"
+#include "TableAccessible.h"
+#include "TableCellAccessible.h"
+#include "nsMai.h"
+#include "ProxyAccessible.h"
+#include "nsArrayUtils.h"
+
+#include "mozilla/Likely.h"
+
+using namespace mozilla::a11y;
+
+extern "C" {
+static gint
+GetColumnSpanCB(AtkTableCell* aCell)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aCell));
+ if (accWrap) {
+ return accWrap->AsTableCell()->ColExtent();
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aCell))) {
+ return proxy->ColExtent();
+ }
+
+ return 0;
+}
+
+static gboolean
+GetRowSpanCB(AtkTableCell* aCell)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aCell));
+ if (accWrap) {
+ return accWrap->AsTableCell()->RowExtent();
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aCell))) {
+ return proxy->RowExtent();
+ }
+
+ return 0;
+}
+
+static gboolean
+GetPositionCB(AtkTableCell* aCell, gint* aRow, gint* aCol)
+{
+ if (AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aCell))) {
+ TableCellAccessible* cell = accWrap->AsTableCell();
+ *aRow = cell->RowIdx();
+ *aCol = cell->ColIdx();
+ return true;
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aCell))) {
+ uint32_t rowIdx = 0, colIdx = 0;
+ proxy->GetPosition(&rowIdx, &colIdx);
+ *aCol = colIdx;
+ *aRow = rowIdx;
+ return true;
+ }
+
+ return false;
+}
+
+static gboolean
+GetColumnRowSpanCB(AtkTableCell* aCell, gint* aCol, gint* aRow,
+ gint* aColExtent, gint* aRowExtent) {
+ if (AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aCell))) {
+ TableCellAccessible* cellAcc = accWrap->AsTableCell();
+ *aCol = cellAcc->ColIdx();
+ *aRow = cellAcc->RowIdx();
+ *aColExtent = cellAcc->ColExtent();
+ *aRowExtent = cellAcc->ColExtent();
+ return true;
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aCell))) {
+ uint32_t colIdx = 0, rowIdx = 0, colExtent = 0, rowExtent = 0;
+ proxy->GetColRowExtents(&colIdx, &rowIdx, &colExtent, &rowExtent);
+ *aCol = colIdx;
+ *aRow = rowIdx;
+ *aColExtent = colExtent;
+ *aRowExtent = rowExtent;
+ return true;
+ }
+
+ return false;
+}
+
+static AtkObject*
+GetTableCB(AtkTableCell* aTableCell)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTableCell));
+ if (accWrap) {
+ TableAccessible* table = accWrap->AsTableCell()->Table();
+ if (!table) {
+ return nullptr;
+ }
+
+ Accessible* tableAcc = table->AsAccessible();
+ return tableAcc ? AccessibleWrap::GetAtkObject(tableAcc) : nullptr;
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTableCell))) {
+ ProxyAccessible* table = proxy->TableOfACell();
+ return table ? GetWrapperFor(table) : nullptr;
+ }
+
+ return nullptr;
+}
+
+static GPtrArray*
+GetColumnHeaderCellsCB(AtkTableCell* aCell)
+{
+ if (AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aCell))) {
+ AutoTArray<Accessible*, 10> headers;
+ accWrap->AsTableCell()->ColHeaderCells(&headers);
+ if (headers.IsEmpty()) {
+ return nullptr;
+ }
+
+ GPtrArray* atkHeaders = g_ptr_array_sized_new(headers.Length());
+ for (Accessible* header: headers) {
+ AtkObject* atkHeader = AccessibleWrap::GetAtkObject(header);
+ g_object_ref(atkHeader);
+ g_ptr_array_add(atkHeaders, atkHeader);
+ }
+
+ return atkHeaders;
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aCell))) {
+ AutoTArray<ProxyAccessible*, 10> headers;
+ proxy->ColHeaderCells(&headers);
+ if (headers.IsEmpty()) {
+ return nullptr;
+ }
+
+ GPtrArray* atkHeaders = g_ptr_array_sized_new(headers.Length());
+ for (ProxyAccessible* header: headers) {
+ AtkObject* atkHeader = GetWrapperFor(header);
+ g_object_ref(atkHeader);
+ g_ptr_array_add(atkHeaders, atkHeader);
+ }
+
+ return atkHeaders;
+ }
+
+ return nullptr;
+}
+
+static GPtrArray*
+GetRowHeaderCellsCB(AtkTableCell* aCell)
+{
+ if (AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aCell))) {
+ AutoTArray<Accessible*, 10> headers;
+ accWrap->AsTableCell()->RowHeaderCells(&headers);
+ if (headers.IsEmpty()) {
+ return nullptr;
+ }
+
+ GPtrArray* atkHeaders = g_ptr_array_sized_new(headers.Length());
+ for (Accessible* header: headers) {
+ AtkObject* atkHeader = AccessibleWrap::GetAtkObject(header);
+ g_object_ref(atkHeader);
+ g_ptr_array_add(atkHeaders, atkHeader);
+ }
+
+ return atkHeaders;
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aCell))) {
+ AutoTArray<ProxyAccessible*, 10> headers;
+ proxy->RowHeaderCells(&headers);
+ if (headers.IsEmpty()) {
+ return nullptr;
+ }
+
+ GPtrArray* atkHeaders = g_ptr_array_sized_new(headers.Length());
+ for (ProxyAccessible* header: headers) {
+ AtkObject* atkHeader = GetWrapperFor(header);
+ g_object_ref(atkHeader);
+ g_ptr_array_add(atkHeaders, atkHeader);
+ }
+
+ return atkHeaders;
+ }
+
+ return nullptr;
+}
+}
+
+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 = GetColumnRowSpanCB;
+ aIface->get_table = GetTableCB;
+}
diff --git a/accessible/atk/nsMaiInterfaceText.cpp b/accessible/atk/nsMaiInterfaceText.cpp
new file mode 100644
index 000000000..1982f3730
--- /dev/null
+++ b/accessible/atk/nsMaiInterfaceText.cpp
@@ -0,0 +1,629 @@
+/* -*- 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 "Accessible-inl.h"
+#include "HyperTextAccessible-inl.h"
+#include "nsMai.h"
+#include "ProxyAccessible.h"
+
+#include "nsIAccessibleTypes.h"
+#include "nsIPersistentProperties2.h"
+#include "nsISimpleEnumerator.h"
+
+#include "mozilla/Likely.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+static const char* sAtkTextAttrNames[ATK_TEXT_ATTR_LAST_DEFINED];
+
+void
+ConvertTextAttributeToAtkAttribute(const nsACString& aName,
+ const nsAString& aValue,
+ AtkAttributeSet** aAttributeSet)
+{
+ // Handle attributes where atk has its own name.
+ const char* atkName = nullptr;
+ nsAutoString atkValue;
+ if (aName.EqualsLiteral("color")) {
+ // The format of the atk attribute is r,g,b and the gecko one is
+ // rgb(r, g, b).
+ atkValue = Substring(aValue, 4, aValue.Length() - 5);
+ atkValue.StripWhitespace();
+ atkName = sAtkTextAttrNames[ATK_TEXT_ATTR_FG_COLOR];
+ } else if (aName.EqualsLiteral("background-color")) {
+ // The format of the atk attribute is r,g,b and the gecko one is
+ // rgb(r, g, b).
+ atkValue = Substring(aValue, 4, aValue.Length() - 5);
+ atkValue.StripWhitespace();
+ atkName = sAtkTextAttrNames[ATK_TEXT_ATTR_BG_COLOR];
+ } else if (aName.EqualsLiteral("font-family")) {
+ atkValue = aValue;
+ atkName = sAtkTextAttrNames[ATK_TEXT_ATTR_FAMILY_NAME];
+ } else if (aName.EqualsLiteral("font-size")) {
+ // ATK wants the number of pixels without px at the end.
+ atkValue = StringHead(aValue, aValue.Length() - 2);
+ atkName = sAtkTextAttrNames[ATK_TEXT_ATTR_SIZE];
+ } else if (aName.EqualsLiteral("font-weight")) {
+ atkValue = aValue;
+ atkName = sAtkTextAttrNames[ATK_TEXT_ATTR_WEIGHT];
+ } else if (aName.EqualsLiteral("invalid")) {
+ atkValue = aValue;
+ atkName = sAtkTextAttrNames[ATK_TEXT_ATTR_INVALID];
+ }
+
+ if (atkName) {
+ AtkAttribute* objAttr =
+ static_cast<AtkAttribute*>(g_malloc(sizeof(AtkAttribute)));
+ objAttr->name = g_strdup(atkName);
+ objAttr->value = g_strdup(NS_ConvertUTF16toUTF8(atkValue).get());
+ *aAttributeSet = g_slist_prepend(*aAttributeSet, objAttr);
+ }
+}
+
+static AtkAttributeSet*
+ConvertToAtkTextAttributeSet(nsTArray<Attribute>& aAttributes)
+{
+ AtkAttributeSet* objAttributeSet = nullptr;
+ for (size_t i = 0; i < aAttributes.Length(); ++i) {
+ AtkAttribute* objAttr = (AtkAttribute *)g_malloc(sizeof(AtkAttribute));
+ objAttr->name = g_strdup(aAttributes[i].Name().get());
+ objAttr->value =
+ g_strdup(NS_ConvertUTF16toUTF8(aAttributes[i].Value()).get());
+ objAttributeSet = g_slist_prepend(objAttributeSet, objAttr);
+ ConvertTextAttributeToAtkAttribute(aAttributes[i].Name(),
+ aAttributes[i].Value(),
+ &objAttributeSet);
+ }
+ return objAttributeSet;
+}
+
+static AtkAttributeSet*
+ConvertToAtkTextAttributeSet(nsIPersistentProperties* aAttributes)
+{
+ if (!aAttributes)
+ return nullptr;
+
+ AtkAttributeSet* objAttributeSet = nullptr;
+ nsCOMPtr<nsISimpleEnumerator> propEnum;
+ nsresult rv = aAttributes->Enumerate(getter_AddRefs(propEnum));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(propEnum->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> sup;
+ rv = propEnum->GetNext(getter_AddRefs(sup));
+ NS_ENSURE_SUCCESS(rv, objAttributeSet);
+
+ nsCOMPtr<nsIPropertyElement> propElem(do_QueryInterface(sup));
+ NS_ENSURE_TRUE(propElem, objAttributeSet);
+
+ nsAutoCString name;
+ rv = propElem->GetKey(name);
+ NS_ENSURE_SUCCESS(rv, objAttributeSet);
+
+ nsAutoString value;
+ rv = propElem->GetValue(value);
+ NS_ENSURE_SUCCESS(rv, objAttributeSet);
+
+ AtkAttribute* objAttr = (AtkAttribute*)g_malloc(sizeof(AtkAttribute));
+ objAttr->name = g_strdup(name.get());
+ objAttr->value = g_strdup(NS_ConvertUTF16toUTF8(value).get());
+ objAttributeSet = g_slist_prepend(objAttributeSet, objAttr);
+
+ ConvertTextAttributeToAtkAttribute(name, value, &objAttributeSet);
+ }
+
+ // libatk-adaptor will free it
+ return objAttributeSet;
+}
+
+static void
+ConvertTexttoAsterisks(AccessibleWrap* accWrap, nsAString& aString)
+{
+ // convert each char to "*" when it's "password text"
+ if (accWrap->NativeRole() == roles::PASSWORD_TEXT) {
+ for (uint32_t i = 0; i < aString.Length(); i++)
+ aString.Replace(i, 1, NS_LITERAL_STRING("*"));
+ }
+}
+
+extern "C" {
+
+static gchar*
+getTextCB(AtkText *aText, gint aStartOffset, gint aEndOffset)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ nsAutoString autoStr;
+ if (accWrap) {
+ HyperTextAccessible* text = accWrap->AsHyperText();
+ if (!text || !text->IsTextRole())
+ return nullptr;
+
+ text->TextSubstring(aStartOffset, aEndOffset, autoStr);
+
+ ConvertTexttoAsterisks(accWrap, autoStr);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+ proxy->TextSubstring(aStartOffset, aEndOffset, autoStr);
+ }
+
+ NS_ConvertUTF16toUTF8 cautoStr(autoStr);
+
+ //copy and return, libspi will free it.
+ return (cautoStr.get()) ? g_strdup(cautoStr.get()) : nullptr;
+}
+
+static gchar*
+getTextAfterOffsetCB(AtkText *aText, gint aOffset,
+ AtkTextBoundary aBoundaryType,
+ gint *aStartOffset, gint *aEndOffset)
+{
+ nsAutoString autoStr;
+ int32_t startOffset = 0, endOffset = 0;
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ if (accWrap) {
+ HyperTextAccessible* text = accWrap->AsHyperText();
+ if (!text || !text->IsTextRole())
+ return nullptr;
+
+ text->TextAfterOffset(aOffset, aBoundaryType, &startOffset, &endOffset, autoStr);
+ ConvertTexttoAsterisks(accWrap, autoStr);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+ proxy->GetTextAfterOffset(aOffset, aBoundaryType, autoStr, &startOffset,
+ &endOffset);
+ }
+
+ *aStartOffset = startOffset;
+ *aEndOffset = endOffset;
+
+ NS_ConvertUTF16toUTF8 cautoStr(autoStr);
+ return (cautoStr.get()) ? g_strdup(cautoStr.get()) : nullptr;
+}
+
+static gchar*
+getTextAtOffsetCB(AtkText *aText, gint aOffset,
+ AtkTextBoundary aBoundaryType,
+ gint *aStartOffset, gint *aEndOffset)
+{
+ nsAutoString autoStr;
+ int32_t startOffset = 0, endOffset = 0;
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ if (accWrap) {
+ HyperTextAccessible* text = accWrap->AsHyperText();
+ if (!text || !text->IsTextRole())
+ return nullptr;
+
+ text->TextAtOffset(aOffset, aBoundaryType, &startOffset, &endOffset, autoStr);
+ ConvertTexttoAsterisks(accWrap, autoStr);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+ proxy->GetTextAtOffset(aOffset, aBoundaryType, autoStr, &startOffset,
+ &endOffset);
+ }
+
+ *aStartOffset = startOffset;
+ *aEndOffset = endOffset;
+
+ NS_ConvertUTF16toUTF8 cautoStr(autoStr);
+ return (cautoStr.get()) ? g_strdup(cautoStr.get()) : nullptr;
+}
+
+static gunichar
+getCharacterAtOffsetCB(AtkText* aText, gint aOffset)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ if (accWrap) {
+ HyperTextAccessible* text = accWrap->AsHyperText();
+ if (!text || !text->IsTextRole()) {
+ return 0;
+ }
+
+ // char16_t is unsigned short in Mozilla, gnuichar is guint32 in glib.
+ return static_cast<gunichar>(text->CharAt(aOffset));
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+ return static_cast<gunichar>(proxy->CharAt(aOffset));
+ }
+
+ return 0;
+}
+
+static gchar*
+getTextBeforeOffsetCB(AtkText *aText, gint aOffset,
+ AtkTextBoundary aBoundaryType,
+ gint *aStartOffset, gint *aEndOffset)
+{
+ nsAutoString autoStr;
+ int32_t startOffset = 0, endOffset = 0;
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ if (accWrap) {
+ HyperTextAccessible* text = accWrap->AsHyperText();
+ if (!text || !text->IsTextRole())
+ return nullptr;
+
+ text->TextBeforeOffset(aOffset, aBoundaryType,
+ &startOffset, &endOffset, autoStr);
+ ConvertTexttoAsterisks(accWrap, autoStr);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+ proxy->GetTextBeforeOffset(aOffset, aBoundaryType, autoStr, &startOffset,
+ &endOffset);
+ }
+
+ *aStartOffset = startOffset;
+ *aEndOffset = endOffset;
+
+ NS_ConvertUTF16toUTF8 cautoStr(autoStr);
+ return (cautoStr.get()) ? g_strdup(cautoStr.get()) : nullptr;
+}
+
+static gint
+getCaretOffsetCB(AtkText *aText)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ if (accWrap) {
+ HyperTextAccessible* text = accWrap->AsHyperText();
+ if (!text || !text->IsTextRole()) {
+ return 0;
+ }
+
+ return static_cast<gint>(text->CaretOffset());
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+ return static_cast<gint>(proxy->CaretOffset());
+ }
+
+ return 0;
+}
+
+static AtkAttributeSet*
+getRunAttributesCB(AtkText *aText, gint aOffset,
+ gint *aStartOffset,
+ gint *aEndOffset)
+{
+ *aStartOffset = -1;
+ *aEndOffset = -1;
+ int32_t startOffset = 0, endOffset = 0;
+
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ if (accWrap) {
+ HyperTextAccessible* text = accWrap->AsHyperText();
+ if (!text || !text->IsTextRole()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ text->TextAttributes(false, aOffset, &startOffset, &endOffset);
+
+ *aStartOffset = startOffset;
+ *aEndOffset = endOffset;
+
+ return ConvertToAtkTextAttributeSet(attributes);
+ }
+
+ ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText));
+ if (!proxy) {
+ return nullptr;
+ }
+
+ AutoTArray<Attribute, 10> attrs;
+ proxy->TextAttributes(false, aOffset, &attrs, &startOffset, &endOffset);
+ *aStartOffset = startOffset;
+ *aEndOffset = endOffset;
+ return ConvertToAtkTextAttributeSet(attrs);
+}
+
+static AtkAttributeSet*
+getDefaultAttributesCB(AtkText *aText)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ if (accWrap) {
+ HyperTextAccessible* text = accWrap->AsHyperText();
+ if (!text || !text->IsTextRole()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIPersistentProperties> attributes = text->DefaultTextAttributes();
+ return ConvertToAtkTextAttributeSet(attributes);
+ }
+
+ ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText));
+ if (!proxy) {
+ return nullptr;
+ }
+
+ AutoTArray<Attribute, 10> attrs;
+ proxy->DefaultTextAttributes(&attrs);
+ return ConvertToAtkTextAttributeSet(attrs);
+}
+
+static void
+getCharacterExtentsCB(AtkText *aText, gint aOffset,
+ gint *aX, gint *aY,
+ gint *aWidth, gint *aHeight,
+ AtkCoordType aCoords)
+{
+ if(!aX || !aY || !aWidth || !aHeight) {
+ return;
+ }
+
+ nsIntRect rect;
+ uint32_t geckoCoordType;
+ if (aCoords == ATK_XY_SCREEN) {
+ geckoCoordType = nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE;
+ } else {
+ geckoCoordType = nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE;
+ }
+
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ if (accWrap) {
+ HyperTextAccessible* text = accWrap->AsHyperText();
+ if (!text || !text->IsTextRole()) {
+ return;
+ }
+
+ rect = text->CharBounds(aOffset, geckoCoordType);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+ rect = proxy->CharBounds(aOffset, geckoCoordType);
+ } else {
+ return;
+ }
+
+ *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;
+ }
+
+ nsIntRect rect;
+ uint32_t geckoCoordType;
+ if (aCoords == ATK_XY_SCREEN) {
+ geckoCoordType = nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE;
+ } else {
+ geckoCoordType = nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE;
+ }
+
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ if(accWrap) {
+ HyperTextAccessible* text = accWrap->AsHyperText();
+ if (!text || !text->IsTextRole()) {
+ return;
+ }
+
+ rect = text->TextBounds(aStartOffset, aEndOffset, geckoCoordType);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+ rect = proxy->TextBounds(aStartOffset, aEndOffset, geckoCoordType);
+ } else {
+ return;
+ }
+
+ aRect->x = rect.x;
+ aRect->y = rect.y;
+ aRect->width = rect.width;
+ aRect->height = rect.height;
+}
+
+static gint
+getCharacterCountCB(AtkText *aText)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ if (accWrap) {
+ HyperTextAccessible* textAcc = accWrap->AsHyperText();
+ return
+ textAcc->IsDefunct() ? 0 : static_cast<gint>(textAcc->CharacterCount());
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+ return proxy->CharacterCount();
+ }
+
+ return 0;
+}
+
+static gint
+getOffsetAtPointCB(AtkText *aText,
+ gint aX, gint aY,
+ AtkCoordType aCoords)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ if (accWrap) {
+ HyperTextAccessible* text = accWrap->AsHyperText();
+ if (!text || !text->IsTextRole()) {
+ return -1;
+ }
+
+ return static_cast<gint>(
+ text->OffsetAtPoint(aX, aY,
+ (aCoords == ATK_XY_SCREEN ?
+ nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE :
+ nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE)));
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+ return static_cast<gint>(
+ proxy->OffsetAtPoint(aX, aY,
+ (aCoords == ATK_XY_SCREEN ?
+ nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE :
+ nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE)));
+ }
+
+ return -1;
+}
+
+static gint
+getTextSelectionCountCB(AtkText *aText)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ if (accWrap) {
+ HyperTextAccessible* text = accWrap->AsHyperText();
+ if (!text || !text->IsTextRole()) {
+ return 0;
+ }
+
+ return text->SelectionCount();
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+ return proxy->SelectionCount();
+ }
+
+ return 0;
+}
+
+static gchar*
+getTextSelectionCB(AtkText *aText, gint aSelectionNum,
+ gint *aStartOffset, gint *aEndOffset)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ int32_t startOffset = 0, endOffset = 0;
+ if (accWrap) {
+ HyperTextAccessible* text = accWrap->AsHyperText();
+ if (!text || !text->IsTextRole()) {
+ return nullptr;
+ }
+
+ text->SelectionBoundsAt(aSelectionNum, &startOffset, &endOffset);
+ *aStartOffset = startOffset;
+ *aEndOffset = endOffset;
+
+ return getTextCB(aText, *aStartOffset, *aEndOffset);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+ nsString data;
+ proxy->SelectionBoundsAt(aSelectionNum, data, &startOffset, &endOffset);
+ *aStartOffset = startOffset;
+ *aEndOffset = endOffset;
+
+ NS_ConvertUTF16toUTF8 dataAsUTF8(data);
+ return (dataAsUTF8.get()) ? g_strdup(dataAsUTF8.get()) : nullptr;
+ }
+ return nullptr;
+}
+
+// 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);
+ } else if (ProxyAccessible* 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);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+ return proxy->RemoveFromSelection(aSelectionNum);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+setTextSelectionCB(AtkText *aText, gint aSelectionNum,
+ gint aStartOffset, gint aEndOffset)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ if (accWrap) {
+ HyperTextAccessible* text = accWrap->AsHyperText();
+ if (!text || !text->IsTextRole()) {
+ return FALSE;
+ }
+
+ return text->SetSelectionBoundsAt(aSelectionNum, aStartOffset, aEndOffset);
+ } else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+ return proxy->SetSelectionBoundsAt(aSelectionNum, aStartOffset, aEndOffset);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+setCaretOffsetCB(AtkText *aText, gint aOffset)
+{
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
+ if (accWrap) {
+ HyperTextAccessible* text = accWrap->AsHyperText();
+ if (!text || !text->IsTextRole() || !text->IsValidOffset(aOffset)) {
+ return FALSE;
+ }
+
+ text->SetCaretOffset(aOffset);
+ return TRUE;
+ }
+
+ if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
+ proxy->SetCaretOffset(aOffset);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+}
+
+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;
+
+ // 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 000000000..016b3b672
--- /dev/null
+++ b/accessible/atk/nsMaiInterfaceValue.cpp
@@ -0,0 +1,133 @@
+/* -*- 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 "ProxyAccessible.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)
+{
+ ProxyAccessible* proxy = nullptr;
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(obj));
+ if (!accWrap) {
+ proxy = GetProxy(ATK_OBJECT(obj));
+ if (!proxy) {
+ return;
+ }
+ }
+
+ memset (value, 0, sizeof (GValue));
+ double accValue = accWrap ? accWrap->CurValue() : proxy->CurValue();
+ if (IsNaN(accValue))
+ return;
+
+ g_value_init (value, G_TYPE_DOUBLE);
+ g_value_set_double (value, accValue);
+}
+
+static void
+getMaximumValueCB(AtkValue *obj, GValue *value)
+{
+ ProxyAccessible* proxy = nullptr;
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(obj));
+ if (!accWrap) {
+ proxy = GetProxy(ATK_OBJECT(obj));
+ if (!proxy) {
+ return;
+ }
+ }
+
+ memset(value, 0, sizeof (GValue));
+ double accValue = accWrap ? accWrap->MaxValue() : proxy->MaxValue();
+ if (IsNaN(accValue))
+ return;
+
+ g_value_init(value, G_TYPE_DOUBLE);
+ g_value_set_double(value, accValue);
+}
+
+static void
+getMinimumValueCB(AtkValue *obj, GValue *value)
+{
+ ProxyAccessible* proxy = nullptr;
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(obj));
+ if (!accWrap) {
+ proxy = GetProxy(ATK_OBJECT(obj));
+ if (!proxy) {
+ return;
+ }
+ }
+
+ memset(value, 0, sizeof (GValue));
+ double accValue = accWrap ? accWrap->MinValue() : proxy->MinValue();
+ if (IsNaN(accValue))
+ return;
+
+ g_value_init(value, G_TYPE_DOUBLE);
+ g_value_set_double(value, accValue);
+}
+
+static void
+getMinimumIncrementCB(AtkValue *obj, GValue *minimumIncrement)
+{
+ ProxyAccessible* proxy = nullptr;
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(obj));
+ if (!accWrap) {
+ proxy = GetProxy(ATK_OBJECT(obj));
+ if (!proxy) {
+ return;
+ }
+ }
+
+ memset(minimumIncrement, 0, sizeof (GValue));
+ double accValue = accWrap ? accWrap->Step() : proxy->Step();
+ if (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)
+{
+ ProxyAccessible* proxy = nullptr;
+ AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(obj));
+ if (!accWrap) {
+ proxy = GetProxy(ATK_OBJECT(obj));
+ if (!proxy) {
+ return FALSE;
+ }
+ }
+
+ double accValue =g_value_get_double(value);
+ return accWrap ? accWrap->SetCurValue(accValue) : proxy->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 000000000..2f3cde240
--- /dev/null
+++ b/accessible/atk/nsStateMap.h
@@ -0,0 +1,117 @@
+/* -*- 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"
+
+/******************************************************************************
+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_HASPOPUP: Object displays a pop-up menu or window when invoked.
+ No ATK equivalent. The accessible state is not
+ currently supported.
+ 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.
+******************************************************************************/
+
+enum EStateMapEntryType {
+ kMapDirectly,
+ kMapOpposite, // For example, UNAVAILABLE is the opposite of ENABLED
+ kNoStateChange, // Don't fire state change event
+ kNoSuchState
+};
+
+const AtkStateType kNone = ATK_STATE_INVALID;
+
+struct AtkStateMap {
+ AtkStateType atkState;
+ EStateMapEntryType stateMapEntryType;
+
+ static int32_t GetStateIndexFor(uint64_t aState)
+ {
+ int32_t stateIndex = -1;
+ while (aState > 0) {
+ ++ stateIndex;
+ aState >>= 1;
+ }
+ return stateIndex; // Returns -1 if not mapped
+ }
+};
+
+
+// Map array from cross platform states to ATK states
+static const AtkStateMap gAtkStateMap[] = { // Cross Platform States
+ { 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
+ { kNone, 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
+ { kNone, kNoSuchState }, // = 1 << 48
+};
diff --git a/accessible/base/ARIAMap.cpp b/accessible/base/ARIAMap.cpp
new file mode 100644
index 000000000..c29d37873
--- /dev/null
+++ b/accessible/base/ARIAMap.cpp
@@ -0,0 +1,1008 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 "ARIAMap.h"
+
+#include "nsAccUtils.h"
+#include "nsCoreUtils.h"
+#include "Role.h"
+#include "States.h"
+
+#include "nsAttrName.h"
+#include "nsWhitespaceTokenizer.h"
+
+#include "mozilla/BinarySearch.h"
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+using namespace mozilla::a11y::aria;
+
+static const uint32_t kGenericAccType = 0;
+
+/**
+ * This list of WAI-defined roles are currently hardcoded.
+ * Eventually we will most likely be loading an RDF resource that contains this information
+ * Using RDF will also allow for role extensibility. See bug 280138.
+ *
+ * Definition of nsRoleMapEntry contains comments explaining this table.
+ *
+ * When no Role enum mapping exists for an ARIA role, the role will be exposed
+ * via the object attribute "xml-roles".
+ */
+
+static const nsRoleMapEntry sWAIRoleMaps[] =
+{
+ { // alert
+ &nsGkAtoms::alert,
+ roles::ALERT,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eAlert,
+ kNoReqStates
+ },
+ { // alertdialog
+ &nsGkAtoms::alertdialog,
+ roles::DIALOG,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // application
+ &nsGkAtoms::application,
+ roles::APPLICATION,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eLandmark,
+ kNoReqStates
+ },
+ { // article
+ &nsGkAtoms::article,
+ roles::DOCUMENT,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eReadonlyUntilEditable
+ },
+ { // banner
+ &nsGkAtoms::banner,
+ roles::NOTHING,
+ kUseNativeRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eLandmark,
+ kNoReqStates
+ },
+ { // button
+ &nsGkAtoms::button,
+ roles::PUSHBUTTON,
+ kUseMapRole,
+ eNoValue,
+ ePressAction,
+ eNoLiveAttr,
+ eButton,
+ kNoReqStates
+ // eARIAPressed is auto applied on any button
+ },
+ { // cell
+ &nsGkAtoms::cell,
+ roles::CELL,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eTableCell,
+ kNoReqStates
+ },
+ { // checkbox
+ &nsGkAtoms::checkbox,
+ roles::CHECKBUTTON,
+ kUseMapRole,
+ eNoValue,
+ eCheckUncheckAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIACheckableMixed,
+ eARIAReadonly
+ },
+ { // columnheader
+ &nsGkAtoms::columnheader,
+ roles::COLUMNHEADER,
+ kUseMapRole,
+ eNoValue,
+ eSortAction,
+ eNoLiveAttr,
+ eTableCell,
+ kNoReqStates,
+ eARIASelectableIfDefined,
+ eARIAReadonlyOrEditableIfDefined
+ },
+ { // combobox
+ &nsGkAtoms::combobox,
+ roles::COMBOBOX,
+ kUseMapRole,
+ eNoValue,
+ eOpenCloseAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ states::COLLAPSED | states::HASPOPUP | states::VERTICAL,
+ eARIAAutoComplete,
+ eARIAReadonly,
+ eARIAOrientation
+ },
+ { // complementary
+ &nsGkAtoms::complementary,
+ roles::NOTHING,
+ kUseNativeRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eLandmark,
+ kNoReqStates
+ },
+ { // contentinfo
+ &nsGkAtoms::contentinfo,
+ roles::NOTHING,
+ kUseNativeRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eLandmark,
+ kNoReqStates
+ },
+ { // dialog
+ &nsGkAtoms::dialog,
+ roles::DIALOG,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // directory
+ &nsGkAtoms::directory,
+ roles::LIST,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eList,
+ kNoReqStates
+ },
+ { // document
+ &nsGkAtoms::document,
+ roles::DOCUMENT,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eReadonlyUntilEditable
+ },
+ { // feed
+ &nsGkAtoms::feed,
+ roles::GROUPING,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // form
+ &nsGkAtoms::form,
+ roles::FORM,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eLandmark,
+ kNoReqStates
+ },
+ { // grid
+ &nsGkAtoms::grid,
+ roles::TABLE,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eSelect | eTable,
+ kNoReqStates,
+ eARIAMultiSelectable,
+ eARIAReadonlyOrEditable,
+ eFocusableUntilDisabled
+ },
+ { // gridcell
+ &nsGkAtoms::gridcell,
+ roles::GRID_CELL,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eTableCell,
+ kNoReqStates,
+ eARIASelectable,
+ eARIAReadonlyOrEditableIfDefined
+ },
+ { // group
+ &nsGkAtoms::group,
+ roles::GROUPING,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // heading
+ &nsGkAtoms::heading,
+ roles::HEADING,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // img
+ &nsGkAtoms::img,
+ roles::GRAPHIC,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // key
+ &nsGkAtoms::key,
+ roles::KEY,
+ kUseMapRole,
+ eNoValue,
+ ePressAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIAPressed
+ },
+ { // link
+ &nsGkAtoms::link,
+ roles::LINK,
+ kUseMapRole,
+ eNoValue,
+ eJumpAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ states::LINKED
+ },
+ { // list
+ &nsGkAtoms::list,
+ roles::LIST,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eList,
+ states::READONLY
+ },
+ { // listbox
+ &nsGkAtoms::listbox,
+ roles::LISTBOX,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eListControl | eSelect,
+ states::VERTICAL,
+ eARIAMultiSelectable,
+ eARIAReadonly,
+ eFocusableUntilDisabled,
+ eARIAOrientation
+ },
+ { // listitem
+ &nsGkAtoms::listitem,
+ roles::LISTITEM,
+ kUseMapRole,
+ eNoValue,
+ eNoAction, // XXX: should depend on state, parent accessible
+ eNoLiveAttr,
+ kGenericAccType,
+ states::READONLY
+ },
+ { // log
+ &nsGkAtoms::log_,
+ roles::NOTHING,
+ kUseNativeRole,
+ eNoValue,
+ eNoAction,
+ ePoliteLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // main
+ &nsGkAtoms::main,
+ roles::NOTHING,
+ kUseNativeRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eLandmark,
+ kNoReqStates
+ },
+ { // marquee
+ &nsGkAtoms::marquee,
+ roles::ANIMATION,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eOffLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // math
+ &nsGkAtoms::math,
+ roles::FLAT_EQUATION,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // menu
+ &nsGkAtoms::menu,
+ roles::MENUPOPUP,
+ kUseMapRole,
+ eNoValue,
+ eNoAction, // XXX: technically accessibles of menupopup role haven't
+ // any action, but menu can be open or close.
+ eNoLiveAttr,
+ kGenericAccType,
+ states::VERTICAL,
+ eARIAOrientation
+ },
+ { // menubar
+ &nsGkAtoms::menubar,
+ roles::MENUBAR,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ states::HORIZONTAL,
+ eARIAOrientation
+ },
+ { // menuitem
+ &nsGkAtoms::menuitem,
+ roles::MENUITEM,
+ kUseMapRole,
+ eNoValue,
+ eClickAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIACheckedMixed
+ },
+ { // menuitemcheckbox
+ &nsGkAtoms::menuitemcheckbox,
+ roles::CHECK_MENU_ITEM,
+ kUseMapRole,
+ eNoValue,
+ eClickAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIACheckableMixed
+ },
+ { // menuitemradio
+ &nsGkAtoms::menuitemradio,
+ roles::RADIO_MENU_ITEM,
+ kUseMapRole,
+ eNoValue,
+ eClickAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIACheckableBool
+ },
+ { // navigation
+ &nsGkAtoms::navigation,
+ roles::NOTHING,
+ kUseNativeRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eLandmark,
+ kNoReqStates
+ },
+ { // none
+ &nsGkAtoms::none,
+ roles::NOTHING,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // note
+ &nsGkAtoms::note_,
+ roles::NOTE,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // option
+ &nsGkAtoms::option,
+ roles::OPTION,
+ kUseMapRole,
+ eNoValue,
+ eSelectAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIASelectable,
+ eARIACheckedMixed
+ },
+ { // presentation
+ &nsGkAtoms::presentation,
+ roles::NOTHING,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // progressbar
+ &nsGkAtoms::progressbar,
+ roles::PROGRESSBAR,
+ kUseMapRole,
+ eHasValueMinMax,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ states::READONLY,
+ eIndeterminateIfNoValue
+ },
+ { // radio
+ &nsGkAtoms::radio,
+ roles::RADIOBUTTON,
+ kUseMapRole,
+ eNoValue,
+ eSelectAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIACheckableBool
+ },
+ { // radiogroup
+ &nsGkAtoms::radiogroup,
+ roles::RADIO_GROUP,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIAOrientation
+ },
+ { // region
+ &nsGkAtoms::region,
+ roles::PANE,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // row
+ &nsGkAtoms::row,
+ roles::ROW,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eTableRow,
+ kNoReqStates,
+ eARIASelectable
+ },
+ { // rowgroup
+ &nsGkAtoms::rowgroup,
+ roles::GROUPING,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // rowheader
+ &nsGkAtoms::rowheader,
+ roles::ROWHEADER,
+ kUseMapRole,
+ eNoValue,
+ eSortAction,
+ eNoLiveAttr,
+ eTableCell,
+ kNoReqStates,
+ eARIASelectableIfDefined,
+ eARIAReadonlyOrEditableIfDefined
+ },
+ { // scrollbar
+ &nsGkAtoms::scrollbar,
+ roles::SCROLLBAR,
+ kUseMapRole,
+ eHasValueMinMax,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ states::VERTICAL,
+ eARIAOrientation,
+ eARIAReadonly
+ },
+ { // search
+ &nsGkAtoms::search,
+ roles::NOTHING,
+ kUseNativeRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eLandmark,
+ kNoReqStates
+ },
+ { // searchbox
+ &nsGkAtoms::searchbox,
+ roles::ENTRY,
+ kUseMapRole,
+ eNoValue,
+ eActivateAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIAAutoComplete,
+ eARIAMultiline,
+ eARIAReadonlyOrEditable
+ },
+ { // separator
+ &nsGkAtoms::separator_,
+ roles::SEPARATOR,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ states::HORIZONTAL,
+ eARIAOrientation
+ },
+ { // slider
+ &nsGkAtoms::slider,
+ roles::SLIDER,
+ kUseMapRole,
+ eHasValueMinMax,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ states::HORIZONTAL,
+ eARIAOrientation,
+ eARIAReadonly
+ },
+ { // spinbutton
+ &nsGkAtoms::spinbutton,
+ roles::SPINBUTTON,
+ kUseMapRole,
+ eHasValueMinMax,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIAReadonly
+ },
+ { // status
+ &nsGkAtoms::status,
+ roles::STATUSBAR,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ ePoliteLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // switch
+ &nsGkAtoms::_switch,
+ roles::SWITCH,
+ kUseMapRole,
+ eNoValue,
+ eCheckUncheckAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIACheckableBool
+ },
+ { // tab
+ &nsGkAtoms::tab,
+ roles::PAGETAB,
+ kUseMapRole,
+ eNoValue,
+ eSwitchAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIASelectable
+ },
+ { // table
+ &nsGkAtoms::table,
+ roles::TABLE,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eTable,
+ kNoReqStates,
+ eARIASelectable
+ },
+ { // tablist
+ &nsGkAtoms::tablist,
+ roles::PAGETABLIST,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eSelect,
+ states::HORIZONTAL,
+ eARIAOrientation
+ },
+ { // tabpanel
+ &nsGkAtoms::tabpanel,
+ roles::PROPERTYPAGE,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // textbox
+ &nsGkAtoms::textbox,
+ roles::ENTRY,
+ kUseMapRole,
+ eNoValue,
+ eActivateAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIAAutoComplete,
+ eARIAMultiline,
+ eARIAReadonlyOrEditable
+ },
+ { // timer
+ &nsGkAtoms::timer,
+ roles::NOTHING,
+ kUseNativeRole,
+ eNoValue,
+ eNoAction,
+ eOffLiveAttr,
+ kNoReqStates
+ },
+ { // toolbar
+ &nsGkAtoms::toolbar,
+ roles::TOOLBAR,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ states::HORIZONTAL,
+ eARIAOrientation
+ },
+ { // tooltip
+ &nsGkAtoms::tooltip,
+ roles::TOOLTIP,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+ },
+ { // tree
+ &nsGkAtoms::tree,
+ roles::OUTLINE,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eSelect,
+ states::VERTICAL,
+ eARIAReadonly,
+ eARIAMultiSelectable,
+ eFocusableUntilDisabled,
+ eARIAOrientation
+ },
+ { // treegrid
+ &nsGkAtoms::treegrid,
+ roles::TREE_TABLE,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ eSelect | eTable,
+ states::VERTICAL,
+ eARIAReadonlyOrEditable,
+ eARIAMultiSelectable,
+ eFocusableUntilDisabled,
+ eARIAOrientation
+ },
+ { // treeitem
+ &nsGkAtoms::treeitem,
+ roles::OUTLINEITEM,
+ kUseMapRole,
+ eNoValue,
+ eActivateAction, // XXX: should expose second 'expand/collapse' action based
+ // on states
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates,
+ eARIASelectable,
+ eARIACheckedMixed
+ }
+};
+
+static const nsRoleMapEntry sLandmarkRoleMap = {
+ &nsGkAtoms::_empty,
+ roles::NOTHING,
+ kUseNativeRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+};
+
+nsRoleMapEntry aria::gEmptyRoleMap = {
+ &nsGkAtoms::_empty,
+ roles::NOTHING,
+ kUseMapRole,
+ eNoValue,
+ eNoAction,
+ eNoLiveAttr,
+ kGenericAccType,
+ kNoReqStates
+};
+
+/**
+ * Universal (Global) states:
+ * The following state rules are applied to any accessible element,
+ * whether there is an ARIA role or not:
+ */
+static const EStateRule sWAIUnivStateMap[] = {
+ eARIABusy,
+ eARIADisabled,
+ eARIAExpanded, // Currently under spec review but precedent exists
+ eARIAHasPopup, // Note this is technically a "property"
+ eARIAInvalid,
+ eARIAModal,
+ eARIARequired, // XXX not global, Bug 553117
+ eARIANone
+};
+
+
+/**
+ * ARIA attribute map for attribute characteristics.
+ * @note ARIA attributes that don't have any flags are not included here.
+ */
+
+struct AttrCharacteristics
+{
+ nsIAtom** attributeName;
+ const uint8_t characteristics;
+};
+
+static const AttrCharacteristics gWAIUnivAttrMap[] = {
+ {&nsGkAtoms::aria_activedescendant, ATTR_BYPASSOBJ },
+ {&nsGkAtoms::aria_atomic, ATTR_BYPASSOBJ_IF_FALSE | ATTR_VALTOKEN | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_busy, ATTR_VALTOKEN | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_checked, ATTR_BYPASSOBJ | ATTR_VALTOKEN }, /* exposes checkable obj attr */
+ {&nsGkAtoms::aria_controls, ATTR_BYPASSOBJ | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_describedby, ATTR_BYPASSOBJ | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_details, ATTR_BYPASSOBJ | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_disabled, ATTR_BYPASSOBJ | ATTR_VALTOKEN | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_dropeffect, ATTR_VALTOKEN | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_errormessage, ATTR_BYPASSOBJ | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_expanded, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
+ {&nsGkAtoms::aria_flowto, ATTR_BYPASSOBJ | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_grabbed, ATTR_VALTOKEN | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_haspopup, ATTR_BYPASSOBJ | ATTR_VALTOKEN | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_hidden, ATTR_BYPASSOBJ | ATTR_VALTOKEN | ATTR_GLOBAL }, /* handled special way */
+ {&nsGkAtoms::aria_invalid, ATTR_BYPASSOBJ | ATTR_VALTOKEN | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_label, ATTR_BYPASSOBJ | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_labelledby, ATTR_BYPASSOBJ | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_level, ATTR_BYPASSOBJ }, /* handled via groupPosition */
+ {&nsGkAtoms::aria_live, ATTR_VALTOKEN | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_modal, ATTR_BYPASSOBJ | ATTR_VALTOKEN | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_multiline, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
+ {&nsGkAtoms::aria_multiselectable, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
+ {&nsGkAtoms::aria_owns, ATTR_BYPASSOBJ | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_orientation, ATTR_VALTOKEN },
+ {&nsGkAtoms::aria_posinset, ATTR_BYPASSOBJ }, /* handled via groupPosition */
+ {&nsGkAtoms::aria_pressed, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
+ {&nsGkAtoms::aria_readonly, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
+ {&nsGkAtoms::aria_relevant, ATTR_BYPASSOBJ | ATTR_GLOBAL },
+ {&nsGkAtoms::aria_required, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
+ {&nsGkAtoms::aria_selected, ATTR_BYPASSOBJ | ATTR_VALTOKEN },
+ {&nsGkAtoms::aria_setsize, ATTR_BYPASSOBJ }, /* handled via groupPosition */
+ {&nsGkAtoms::aria_sort, ATTR_VALTOKEN },
+ {&nsGkAtoms::aria_valuenow, ATTR_BYPASSOBJ },
+ {&nsGkAtoms::aria_valuemin, ATTR_BYPASSOBJ },
+ {&nsGkAtoms::aria_valuemax, ATTR_BYPASSOBJ },
+ {&nsGkAtoms::aria_valuetext, ATTR_BYPASSOBJ }
+};
+
+namespace {
+
+struct RoleComparator
+{
+ const nsDependentSubstring& mRole;
+ explicit RoleComparator(const nsDependentSubstring& aRole) : mRole(aRole) {}
+ int operator()(const nsRoleMapEntry& aEntry) const {
+ return Compare(mRole, aEntry.ARIARoleString());
+ }
+};
+
+}
+
+const nsRoleMapEntry*
+aria::GetRoleMap(dom::Element* aEl)
+{
+ return GetRoleMapFromIndex(GetRoleMapIndex(aEl));
+}
+
+uint8_t
+aria::GetRoleMapIndex(dom::Element* aEl)
+{
+ nsAutoString roles;
+ if (!aEl || !aEl->GetAttr(kNameSpaceID_None, nsGkAtoms::role, roles) ||
+ roles.IsEmpty()) {
+ // We treat role="" as if the role attribute is absent (per aria spec:8.1.1)
+ return NO_ROLE_MAP_ENTRY_INDEX;
+ }
+
+ nsWhitespaceTokenizer tokenizer(roles);
+ while (tokenizer.hasMoreTokens()) {
+ // Do a binary search through table for the next role in role list
+ const nsDependentSubstring role = tokenizer.nextToken();
+ size_t idx;
+ if (BinarySearchIf(sWAIRoleMaps, 0, ArrayLength(sWAIRoleMaps),
+ RoleComparator(role), &idx)) {
+ return idx;
+ }
+ }
+
+ // Always use some entry index if there is a non-empty role string
+ // To ensure an accessible object is created
+ return LANDMARK_ROLE_MAP_ENTRY_INDEX;
+}
+
+
+const nsRoleMapEntry*
+aria::GetRoleMapFromIndex(uint8_t aRoleMapIndex)
+{
+ switch (aRoleMapIndex) {
+ case NO_ROLE_MAP_ENTRY_INDEX:
+ return nullptr;
+ case EMPTY_ROLE_MAP_ENTRY_INDEX:
+ return &gEmptyRoleMap;
+ case LANDMARK_ROLE_MAP_ENTRY_INDEX:
+ return &sLandmarkRoleMap;
+ default:
+ return sWAIRoleMaps + aRoleMapIndex;
+ }
+}
+
+uint8_t
+aria::GetIndexFromRoleMap(const nsRoleMapEntry* aRoleMapEntry)
+{
+ if (aRoleMapEntry == nullptr) {
+ return NO_ROLE_MAP_ENTRY_INDEX;
+ } else if (aRoleMapEntry == &gEmptyRoleMap) {
+ return EMPTY_ROLE_MAP_ENTRY_INDEX;
+ } else if (aRoleMapEntry == &sLandmarkRoleMap) {
+ return LANDMARK_ROLE_MAP_ENTRY_INDEX;
+ } else {
+ return aRoleMapEntry - sWAIRoleMaps;
+ }
+}
+
+uint64_t
+aria::UniversalStatesFor(mozilla::dom::Element* aElement)
+{
+ uint64_t state = 0;
+ uint32_t index = 0;
+ while (MapToState(sWAIUnivStateMap[index], aElement, &state))
+ index++;
+
+ return state;
+}
+
+uint8_t
+aria::AttrCharacteristicsFor(nsIAtom* aAtom)
+{
+ for (uint32_t i = 0; i < ArrayLength(gWAIUnivAttrMap); i++)
+ if (*gWAIUnivAttrMap[i].attributeName == aAtom)
+ return gWAIUnivAttrMap[i].characteristics;
+
+ return 0;
+}
+
+bool
+aria::HasDefinedARIAHidden(nsIContent* aContent)
+{
+ return aContent &&
+ nsAccUtils::HasDefinedARIAToken(aContent, nsGkAtoms::aria_hidden) &&
+ !aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_hidden,
+ nsGkAtoms::_false, eCaseMatters);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AttrIterator class
+
+bool
+AttrIterator::Next(nsAString& aAttrName, nsAString& aAttrValue)
+{
+ while (mAttrIdx < mAttrCount) {
+ const nsAttrName* attr = mContent->GetAttrNameAt(mAttrIdx);
+ mAttrIdx++;
+ if (attr->NamespaceEquals(kNameSpaceID_None)) {
+ nsIAtom* attrAtom = attr->Atom();
+ nsDependentAtomString attrStr(attrAtom);
+ if (!StringBeginsWith(attrStr, NS_LITERAL_STRING("aria-")))
+ continue; // Not ARIA
+
+ uint8_t attrFlags = aria::AttrCharacteristicsFor(attrAtom);
+ if (attrFlags & ATTR_BYPASSOBJ)
+ continue; // No need to handle exposing as obj attribute here
+
+ if ((attrFlags & ATTR_VALTOKEN) &&
+ !nsAccUtils::HasDefinedARIAToken(mContent, attrAtom))
+ continue; // only expose token based attributes if they are defined
+
+ if ((attrFlags & ATTR_BYPASSOBJ_IF_FALSE) &&
+ mContent->AttrValueIs(kNameSpaceID_None, attrAtom,
+ nsGkAtoms::_false, eCaseMatters)) {
+ continue; // only expose token based attribute if value is not 'false'.
+ }
+
+ nsAutoString value;
+ if (mContent->GetAttr(kNameSpaceID_None, attrAtom, value)) {
+ aAttrName.Assign(Substring(attrStr, 5));
+ aAttrValue.Assign(value);
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
diff --git a/accessible/base/ARIAMap.h b/accessible/base/ARIAMap.h
new file mode 100644
index 000000000..4d603c04b
--- /dev/null
+++ b/accessible/base/ARIAMap.h
@@ -0,0 +1,305 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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_aria_ARIAMap_h_
+#define mozilla_a11y_aria_ARIAMap_h_
+
+#include "ARIAStateMap.h"
+#include "mozilla/a11y/AccTypes.h"
+#include "mozilla/a11y/Role.h"
+
+#include "nsIAtom.h"
+#include "nsIContent.h"
+
+class nsINode;
+
+////////////////////////////////////////////////////////////////////////////////
+// Value constants
+
+/**
+ * Used to define if role requires to expose Value interface.
+ */
+enum EValueRule
+{
+ /**
+ * Value interface isn't exposed.
+ */
+ eNoValue,
+
+ /**
+ * Value interface is implemented, supports value, min and max from
+ * aria-valuenow, aria-valuemin and aria-valuemax.
+ */
+ eHasValueMinMax
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Action constants
+
+/**
+ * Used to define if the role requires to expose action.
+ */
+enum EActionRule
+{
+ eNoAction,
+ eActivateAction,
+ eClickAction,
+ ePressAction,
+ eCheckUncheckAction,
+ eExpandAction,
+ eJumpAction,
+ eOpenCloseAction,
+ eSelectAction,
+ eSortAction,
+ eSwitchAction
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Live region constants
+
+/**
+ * Used to define if role exposes default value of aria-live attribute.
+ */
+enum ELiveAttrRule
+{
+ eNoLiveAttr,
+ eOffLiveAttr,
+ ePoliteLiveAttr
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Role constants
+
+/**
+ * ARIA role overrides role from native markup.
+ */
+const bool kUseMapRole = true;
+
+/**
+ * ARIA role doesn't override the role from native markup.
+ */
+const bool kUseNativeRole = false;
+
+
+////////////////////////////////////////////////////////////////////////////////
+// ARIA attribute characteristic masks
+
+/**
+ * This mask indicates the attribute should not be exposed as an object
+ * attribute via the catch-all logic in Accessible::Attributes().
+ * This means it either isn't mean't to be exposed as an object attribute, or
+ * that it should, but is already handled in other code.
+ */
+const uint8_t ATTR_BYPASSOBJ = 0x1 << 0;
+const uint8_t ATTR_BYPASSOBJ_IF_FALSE = 0x1 << 1;
+
+/**
+ * This mask indicates the attribute is expected to have an NMTOKEN or bool value.
+ * (See for example usage in Accessible::Attributes())
+ */
+const uint8_t ATTR_VALTOKEN = 0x1 << 2;
+
+/**
+ * Indicate the attribute is global state or property (refer to
+ * http://www.w3.org/TR/wai-aria/states_and_properties#global_states).
+ */
+const uint8_t ATTR_GLOBAL = 0x1 << 3;
+
+////////////////////////////////////////////////////////////////////////////////
+// State map entry
+
+/**
+ * Used in nsRoleMapEntry.state if no nsIAccessibleStates are automatic for
+ * a given role.
+ */
+#define kNoReqStates 0
+
+////////////////////////////////////////////////////////////////////////////////
+// Role map entry
+
+/**
+ * For each ARIA role, this maps the nsIAccessible information.
+ */
+struct nsRoleMapEntry
+{
+ /**
+ * Return true if matches to the given ARIA role.
+ */
+ bool Is(nsIAtom* aARIARole) const
+ { return *roleAtom == aARIARole; }
+
+ /**
+ * Return true if ARIA role has the given accessible type.
+ */
+ bool IsOfType(mozilla::a11y::AccGenericType aType) const
+ { return accTypes & aType; }
+
+ /**
+ * Return ARIA role.
+ */
+ const nsDependentAtomString ARIARoleString() const
+ { return nsDependentAtomString(*roleAtom); }
+
+ // ARIA role: string representation such as "button"
+ nsIAtom** roleAtom;
+
+ // Role mapping rule: maps to enum Role
+ mozilla::a11y::role role;
+
+ // Role rule: whether to use mapped role or native semantics
+ bool roleRule;
+
+ // Value mapping rule: how to compute accessible value
+ EValueRule valueRule;
+
+ // Action mapping rule, how to expose accessible action
+ EActionRule actionRule;
+
+ // 'live' and 'container-live' object attributes mapping rule: how to expose
+ // these object attributes if ARIA 'live' attribute is missed.
+ ELiveAttrRule liveAttRule;
+
+ // Accessible types this role belongs to.
+ uint32_t accTypes;
+
+ // Automatic state mapping rule: always include in states
+ uint64_t state; // or kNoReqStates if no default state for this role
+
+ // ARIA properties supported for this role (in other words, the aria-foo
+ // attribute to accessible states mapping rules).
+ // Currently you cannot have unlimited mappings, because
+ // a variable sized array would not allow the use of
+ // C++'s struct initialization feature.
+ mozilla::a11y::aria::EStateRule attributeMap1;
+ mozilla::a11y::aria::EStateRule attributeMap2;
+ mozilla::a11y::aria::EStateRule attributeMap3;
+ mozilla::a11y::aria::EStateRule attributeMap4;
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+// ARIA map
+
+/**
+ * These provide the mappings for WAI-ARIA roles, states and properties using
+ * the structs defined in this file and ARIAStateMap files.
+ */
+namespace mozilla {
+namespace a11y {
+namespace aria {
+
+/**
+ * Empty role map entry. Used by accessibility service to create an accessible
+ * if the accessible can't use role of used accessible class. For example,
+ * it is used for table cells that aren't contained by table.
+ */
+extern nsRoleMapEntry gEmptyRoleMap;
+
+/**
+ * Constants for the role map entry index to indicate that the role map entry
+ * isn't in sWAIRoleMaps, but rather is a special entry: nullptr,
+ * gEmptyRoleMap, and sLandmarkRoleMap
+ */
+const uint8_t NO_ROLE_MAP_ENTRY_INDEX = UINT8_MAX - 2;
+const uint8_t EMPTY_ROLE_MAP_ENTRY_INDEX = UINT8_MAX - 1;
+const uint8_t LANDMARK_ROLE_MAP_ENTRY_INDEX = UINT8_MAX;
+
+/**
+ * Get the role map entry for a given DOM node. This will use the first
+ * ARIA role if the role attribute provides a space delimited list of roles.
+ *
+ * @param aEl [in] the DOM node to get the role map entry for
+ * @return a pointer to the role map entry for the ARIA role, or nullptr
+ * if none
+ */
+const nsRoleMapEntry* GetRoleMap(dom::Element* aEl);
+
+/**
+ * Get the role map entry pointer's index for a given DOM node. This will use
+ * the first ARIA role if the role attribute provides a space delimited list of
+ * roles.
+ *
+ * @param aEl [in] the DOM node to get the role map entry for
+ * @return the index of the pointer to the role map entry for the ARIA
+ * role, or NO_ROLE_MAP_ENTRY_INDEX if none
+ */
+uint8_t GetRoleMapIndex(dom::Element* aEl);
+
+/**
+ * Get the role map entry pointer for a given role map entry index.
+ *
+ * @param aRoleMapIndex [in] the role map index to get the role map entry
+ * pointer for
+ * @return a pointer to the role map entry for the ARIA role,
+ * or nullptr, if none
+ */
+const nsRoleMapEntry* GetRoleMapFromIndex(uint8_t aRoleMapIndex);
+
+/**
+ * Get the role map entry index for a given role map entry pointer. If the role
+ * map entry is within sWAIRoleMaps, return the index within that array,
+ * otherwise return one of the special index constants listed above.
+ *
+ * @param aRoleMap [in] the role map entry pointer to get the index for
+ * @return the index of the pointer to the role map entry, or
+ * NO_ROLE_MAP_ENTRY_INDEX if none
+ */
+uint8_t GetIndexFromRoleMap(const nsRoleMapEntry* aRoleMap);
+
+/**
+ * Return accessible state from ARIA universal states applied to the given
+ * element.
+ */
+uint64_t UniversalStatesFor(mozilla::dom::Element* aElement);
+
+/**
+ * Get the ARIA attribute characteristics for a given ARIA attribute.
+ *
+ * @param aAtom ARIA attribute
+ * @return A bitflag representing the attribute characteristics
+ * (see above for possible bit masks, prefixed "ATTR_")
+ */
+uint8_t AttrCharacteristicsFor(nsIAtom* aAtom);
+
+/**
+ * Return true if the element has defined aria-hidden.
+ */
+bool HasDefinedARIAHidden(nsIContent* aContent);
+
+ /**
+ * Represents a simple enumerator for iterating through ARIA attributes
+ * exposed as object attributes on a given accessible.
+ */
+class AttrIterator
+{
+public:
+ explicit AttrIterator(nsIContent* aContent) :
+ mContent(aContent), mAttrIdx(0)
+ {
+ mAttrCount = mContent->GetAttrCount();
+ }
+
+ bool Next(nsAString& aAttrName, nsAString& aAttrValue);
+
+private:
+ AttrIterator() = delete;
+ AttrIterator(const AttrIterator&) = delete;
+ AttrIterator& operator= (const AttrIterator&) = delete;
+
+ nsIContent* mContent;
+ uint32_t mAttrIdx;
+ uint32_t mAttrCount;
+};
+
+} // namespace aria
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/ARIAStateMap.cpp b/accessible/base/ARIAStateMap.cpp
new file mode 100644
index 000000000..f121835d1
--- /dev/null
+++ b/accessible/base/ARIAStateMap.cpp
@@ -0,0 +1,376 @@
+/* -*- 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 "ARIAMap.h"
+#include "nsAccUtils.h"
+#include "States.h"
+
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+using namespace mozilla::a11y::aria;
+
+/**
+ * Used to store state map rule data for ARIA attribute of enum type.
+ */
+struct EnumTypeData
+{
+ // ARIA attribute name.
+ nsIAtom* const mAttrName;
+
+ // States if the attribute value is matched to the enum value. Used as
+ // nsIContent::AttrValuesArray, last item must be nullptr.
+ nsIAtom* const* const mValues[4];
+
+ // States applied if corresponding enum values are matched.
+ const uint64_t mStates[3];
+
+ // States to clear in case of match.
+ const uint64_t mClearState;
+};
+
+enum ETokenType
+{
+ eBoolType = 0,
+ eMixedType = 1, // can take 'mixed' value
+ eDefinedIfAbsent = 2 // permanent and false state are applied if absent
+};
+
+/**
+ * Used to store state map rule data for ARIA attribute of token type (including
+ * mixed value).
+ */
+struct TokenTypeData
+{
+ TokenTypeData(nsIAtom* aAttrName, uint32_t aType,
+ uint64_t aPermanentState,
+ uint64_t aTrueState,
+ uint64_t aFalseState = 0) :
+ mAttrName(aAttrName), mType(aType), mPermanentState(aPermanentState),
+ mTrueState(aTrueState), mFalseState(aFalseState)
+ { }
+
+ // ARIA attribute name.
+ nsIAtom* const mAttrName;
+
+ // Type.
+ const uint32_t mType;
+
+ // State applied if the attribute is defined or mType doesn't have
+ // eDefinedIfAbsent flag set.
+ const uint64_t mPermanentState;
+
+ // States applied if the attribute value is true/false.
+ const uint64_t mTrueState;
+ const uint64_t mFalseState;
+};
+
+/**
+ * Map enum type attribute value to accessible state.
+ */
+static void MapEnumType(dom::Element* aElement, uint64_t* aState,
+ const EnumTypeData& aData);
+
+/**
+ * Map token type attribute value to states.
+ */
+static void MapTokenType(dom::Element* aContent, uint64_t* aState,
+ const TokenTypeData& aData);
+
+bool
+aria::MapToState(EStateRule aRule, dom::Element* aElement, uint64_t* aState)
+{
+ switch (aRule) {
+ case eARIAAutoComplete:
+ {
+ static const EnumTypeData data = {
+ nsGkAtoms::aria_autocomplete,
+ { &nsGkAtoms::inlinevalue,
+ &nsGkAtoms::list,
+ &nsGkAtoms::both, nullptr },
+ { states::SUPPORTS_AUTOCOMPLETION,
+ states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION,
+ states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION }, 0
+ };
+
+ MapEnumType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIABusy:
+ {
+ static const EnumTypeData data = {
+ nsGkAtoms::aria_busy,
+ { &nsGkAtoms::_true,
+ &nsGkAtoms::error, nullptr },
+ { states::BUSY,
+ states::INVALID }, 0
+ };
+
+ MapEnumType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIACheckableBool:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_checked, eBoolType | eDefinedIfAbsent,
+ states::CHECKABLE, states::CHECKED);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIACheckableMixed:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_checked, eMixedType | eDefinedIfAbsent,
+ states::CHECKABLE, states::CHECKED);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIACheckedMixed:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_checked, eMixedType,
+ states::CHECKABLE, states::CHECKED);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIADisabled:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_disabled, eBoolType,
+ 0, states::UNAVAILABLE);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIAExpanded:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_expanded, eBoolType,
+ 0, states::EXPANDED, states::COLLAPSED);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIAHasPopup:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_haspopup, eBoolType,
+ 0, states::HASPOPUP);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIAInvalid:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_invalid, eBoolType,
+ 0, states::INVALID);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIAModal:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_modal, eBoolType,
+ 0, states::MODAL);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIAMultiline:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_multiline, eBoolType | eDefinedIfAbsent,
+ 0, states::MULTI_LINE, states::SINGLE_LINE);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIAMultiSelectable:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_multiselectable, eBoolType,
+ 0, states::MULTISELECTABLE | states::EXTSELECTABLE);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIAOrientation:
+ {
+ static const EnumTypeData data = {
+ nsGkAtoms::aria_orientation,
+ { &nsGkAtoms::horizontal,
+ &nsGkAtoms::vertical, nullptr },
+ { states::HORIZONTAL,
+ states::VERTICAL },
+ states::HORIZONTAL | states::VERTICAL
+ };
+
+ MapEnumType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIAPressed:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_pressed, eMixedType,
+ 0, states::PRESSED);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIAReadonly:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_readonly, eBoolType,
+ 0, states::READONLY);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIAReadonlyOrEditable:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_readonly, eBoolType | eDefinedIfAbsent,
+ 0, states::READONLY, states::EDITABLE);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIAReadonlyOrEditableIfDefined:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_readonly, eBoolType,
+ 0, states::READONLY, states::EDITABLE);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIARequired:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_required, eBoolType,
+ 0, states::REQUIRED);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIASelectable:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_selected, eBoolType | eDefinedIfAbsent,
+ states::SELECTABLE, states::SELECTED);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eARIASelectableIfDefined:
+ {
+ static const TokenTypeData data(
+ nsGkAtoms::aria_selected, eBoolType,
+ states::SELECTABLE, states::SELECTED);
+
+ MapTokenType(aElement, aState, data);
+ return true;
+ }
+
+ case eReadonlyUntilEditable:
+ {
+ if (!(*aState & states::EDITABLE))
+ *aState |= states::READONLY;
+
+ return true;
+ }
+
+ case eIndeterminateIfNoValue:
+ {
+ if (!aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuenow) &&
+ !aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext))
+ *aState |= states::MIXED;
+
+ return true;
+ }
+
+ case eFocusableUntilDisabled:
+ {
+ if (!nsAccUtils::HasDefinedARIAToken(aElement, nsGkAtoms::aria_disabled) ||
+ aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_disabled,
+ nsGkAtoms::_false, eCaseMatters))
+ *aState |= states::FOCUSABLE;
+
+ return true;
+ }
+
+ default:
+ return false;
+ }
+}
+
+static void
+MapEnumType(dom::Element* aElement, uint64_t* aState, const EnumTypeData& aData)
+{
+ switch (aElement->FindAttrValueIn(kNameSpaceID_None, aData.mAttrName,
+ aData.mValues, eCaseMatters)) {
+ case 0:
+ *aState = (*aState & ~aData.mClearState) | aData.mStates[0];
+ return;
+ case 1:
+ *aState = (*aState & ~aData.mClearState) | aData.mStates[1];
+ return;
+ case 2:
+ *aState = (*aState & ~aData.mClearState) | aData.mStates[2];
+ return;
+ }
+}
+
+static void
+MapTokenType(dom::Element* aElement, uint64_t* aState,
+ const TokenTypeData& aData)
+{
+ if (nsAccUtils::HasDefinedARIAToken(aElement, aData.mAttrName)) {
+ if ((aData.mType & eMixedType) &&
+ aElement->AttrValueIs(kNameSpaceID_None, aData.mAttrName,
+ nsGkAtoms::mixed, eCaseMatters)) {
+ *aState |= aData.mPermanentState | states::MIXED;
+ return;
+ }
+
+ if (aElement->AttrValueIs(kNameSpaceID_None, aData.mAttrName,
+ nsGkAtoms::_false, eCaseMatters)) {
+ *aState |= aData.mPermanentState | aData.mFalseState;
+ return;
+ }
+
+ *aState |= aData.mPermanentState | aData.mTrueState;
+ return;
+ }
+
+ if (aData.mType & eDefinedIfAbsent)
+ *aState |= aData.mPermanentState | aData.mFalseState;
+}
diff --git a/accessible/base/ARIAStateMap.h b/accessible/base/ARIAStateMap.h
new file mode 100644
index 000000000..41626fcaf
--- /dev/null
+++ b/accessible/base/ARIAStateMap.h
@@ -0,0 +1,67 @@
+/* -*- 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_aria_ARIAStateMap_h_
+#define _mozilla_a11y_aria_ARIAStateMap_h_
+
+#include <stdint.h>
+
+namespace mozilla {
+
+namespace dom {
+class Element;
+}
+
+namespace a11y {
+namespace aria {
+
+/**
+ * List of the ARIA state mapping rules.
+ */
+enum EStateRule
+{
+ eARIANone,
+ eARIAAutoComplete,
+ eARIABusy,
+ eARIACheckableBool,
+ eARIACheckableMixed,
+ eARIACheckedMixed,
+ eARIADisabled,
+ eARIAExpanded,
+ eARIAHasPopup,
+ eARIAInvalid,
+ eARIAModal,
+ eARIAMultiline,
+ eARIAMultiSelectable,
+ eARIAOrientation,
+ eARIAPressed,
+ eARIAReadonly,
+ eARIAReadonlyOrEditable,
+ eARIAReadonlyOrEditableIfDefined,
+ eARIARequired,
+ eARIASelectable,
+ eARIASelectableIfDefined,
+ eReadonlyUntilEditable,
+ eIndeterminateIfNoValue,
+ eFocusableUntilDisabled
+};
+
+/**
+ * Expose the accessible states for the given element accordingly to state
+ * mapping rule.
+ *
+ * @param aRule [in] state mapping rule ID
+ * @param aElement [in] node of the accessible
+ * @param aState [in/out] accessible states
+ * @return true if state map rule ID is valid
+ */
+bool MapToState(EStateRule aRule, dom::Element* aElement, uint64_t* aState);
+
+} // namespace aria
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/AccEvent.cpp b/accessible/base/AccEvent.cpp
new file mode 100644
index 000000000..a27ceacb9
--- /dev/null
+++ b/accessible/base/AccEvent.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 "AccEvent.h"
+
+#include "nsAccUtils.h"
+#include "DocAccessible.h"
+#include "xpcAccEvents.h"
+#include "States.h"
+#include "xpcAccessibleDocument.h"
+
+#include "mozilla/EventStateManager.h"
+#include "mozilla/dom/Selection.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+static_assert(static_cast<bool>(eNoUserInput) == false &&
+ static_cast<bool>(eFromUserInput) == true,
+ "EIsFromUserInput cannot be casted to bool");
+
+////////////////////////////////////////////////////////////////////////////////
+// AccEvent
+////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////
+// AccEvent constructors
+
+AccEvent::AccEvent(uint32_t aEventType, Accessible* aAccessible,
+ EIsFromUserInput aIsFromUserInput, EEventRule aEventRule) :
+ mEventType(aEventType), mEventRule(aEventRule), mAccessible(aAccessible)
+{
+ if (aIsFromUserInput == eAutoDetect)
+ mIsFromUserInput = EventStateManager::IsHandlingUserInput();
+ else
+ mIsFromUserInput = aIsFromUserInput == eFromUserInput ? true : false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AccEvent cycle collection
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(AccEvent)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AccEvent)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessible)
+ if (AccTreeMutationEvent* tmEvent = downcast_accEvent(tmp)) {
+ tmEvent->SetNextEvent(nullptr);
+ tmEvent->SetPrevEvent(nullptr);
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AccEvent)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessible)
+ if (AccTreeMutationEvent* tmEvent = downcast_accEvent(tmp)) {
+ CycleCollectionNoteChild(cb, tmEvent->NextEvent(), "mNext");
+ CycleCollectionNoteChild(cb, tmEvent->PrevEvent(), "mPrevEvent");
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AccEvent, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AccEvent, Release)
+
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+// AccTextChangeEvent
+////////////////////////////////////////////////////////////////////////////////
+
+// Note: we pass in eAllowDupes to the base class because we don't support text
+// events coalescence. We fire delayed text change events in DocAccessible but
+// we continue to base the event off the accessible object rather than just the
+// node. This means we won't try to create an accessible based on the node when
+// we are ready to fire the event and so we will no longer assert at that point
+// if the node was removed from the document. Either way, the AT won't work with
+// a defunct accessible so the behaviour should be equivalent.
+AccTextChangeEvent::
+ AccTextChangeEvent(Accessible* aAccessible, int32_t aStart,
+ const nsAString& aModifiedText, bool aIsInserted,
+ EIsFromUserInput aIsFromUserInput)
+ : AccEvent(aIsInserted ?
+ static_cast<uint32_t>(nsIAccessibleEvent::EVENT_TEXT_INSERTED) :
+ static_cast<uint32_t>(nsIAccessibleEvent::EVENT_TEXT_REMOVED),
+ aAccessible, aIsFromUserInput, eAllowDupes)
+ , mStart(aStart)
+ , mIsInserted(aIsInserted)
+ , mModifiedText(aModifiedText)
+{
+ // XXX We should use IsFromUserInput here, but that isn't always correct
+ // when the text change isn't related to content insertion or removal.
+ mIsFromUserInput = mAccessible->State() &
+ (states::FOCUSED | states::EDITABLE);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AccHideEvent
+////////////////////////////////////////////////////////////////////////////////
+
+AccHideEvent::
+ AccHideEvent(Accessible* aTarget, bool aNeedsShutdown) :
+ AccMutationEvent(::nsIAccessibleEvent::EVENT_HIDE, aTarget),
+ mNeedsShutdown(aNeedsShutdown)
+{
+ mNextSibling = mAccessible->NextSibling();
+ mPrevSibling = mAccessible->PrevSibling();
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// AccShowEvent
+////////////////////////////////////////////////////////////////////////////////
+
+AccShowEvent::
+ AccShowEvent(Accessible* aTarget) :
+ AccMutationEvent(::nsIAccessibleEvent::EVENT_SHOW, aTarget)
+{
+ int32_t idx = aTarget->IndexInParent();
+ MOZ_ASSERT(idx >= 0);
+ mInsertionIndex = idx;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// AccTextSelChangeEvent
+////////////////////////////////////////////////////////////////////////////////
+
+AccTextSelChangeEvent::AccTextSelChangeEvent(HyperTextAccessible* aTarget,
+ dom::Selection* aSelection,
+ int32_t aReason) :
+ AccEvent(nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED, aTarget,
+ eAutoDetect, eCoalesceTextSelChange),
+ mSel(aSelection), mReason(aReason) {}
+
+AccTextSelChangeEvent::~AccTextSelChangeEvent() { }
+
+bool
+AccTextSelChangeEvent::IsCaretMoveOnly() const
+{
+ return mSel->RangeCount() == 1 && mSel->IsCollapsed() &&
+ ((mReason & (nsISelectionListener::COLLAPSETOSTART_REASON |
+ nsISelectionListener::COLLAPSETOEND_REASON)) == 0);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AccSelChangeEvent
+////////////////////////////////////////////////////////////////////////////////
+
+AccSelChangeEvent::
+ AccSelChangeEvent(Accessible* aWidget, Accessible* aItem,
+ SelChangeType aSelChangeType) :
+ AccEvent(0, aItem, eAutoDetect, eCoalesceSelectionChange),
+ mWidget(aWidget), mItem(aItem), mSelChangeType(aSelChangeType),
+ mPreceedingCount(0), mPackedEvent(nullptr)
+{
+ if (aSelChangeType == eSelectionAdd) {
+ if (mWidget->GetSelectedItem(1))
+ mEventType = nsIAccessibleEvent::EVENT_SELECTION_ADD;
+ else
+ mEventType = nsIAccessibleEvent::EVENT_SELECTION;
+ } else {
+ mEventType = nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// AccTableChangeEvent
+////////////////////////////////////////////////////////////////////////////////
+
+AccTableChangeEvent::
+ AccTableChangeEvent(Accessible* aAccessible, uint32_t aEventType,
+ int32_t aRowOrColIndex, int32_t aNumRowsOrCols) :
+ AccEvent(aEventType, aAccessible),
+ mRowOrColIndex(aRowOrColIndex), mNumRowsOrCols(aNumRowsOrCols)
+{
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// AccVCChangeEvent
+////////////////////////////////////////////////////////////////////////////////
+
+AccVCChangeEvent::
+ AccVCChangeEvent(Accessible* aAccessible,
+ Accessible* aOldAccessible,
+ int32_t aOldStart, int32_t aOldEnd,
+ int16_t aReason, EIsFromUserInput aIsFromUserInput) :
+ AccEvent(::nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED, aAccessible,
+ aIsFromUserInput),
+ mOldAccessible(aOldAccessible), mOldStart(aOldStart), mOldEnd(aOldEnd),
+ mReason(aReason)
+{
+}
+
+already_AddRefed<nsIAccessibleEvent>
+a11y::MakeXPCEvent(AccEvent* aEvent)
+{
+ DocAccessible* doc = aEvent->Document();
+ Accessible* acc = aEvent->GetAccessible();
+ nsINode* node = acc->GetNode();
+ nsIDOMNode* domNode = node ? node->AsDOMNode() : nullptr;
+ bool fromUser = aEvent->IsFromUserInput();
+ uint32_t type = aEvent->GetEventType();
+ uint32_t eventGroup = aEvent->GetEventGroups();
+ nsCOMPtr<nsIAccessibleEvent> xpEvent;
+
+ if (eventGroup & (1 << AccEvent::eStateChangeEvent)) {
+ AccStateChangeEvent* sc = downcast_accEvent(aEvent);
+ bool extra = false;
+ uint32_t state = nsAccUtils::To32States(sc->GetState(), &extra);
+ xpEvent = new xpcAccStateChangeEvent(type, ToXPC(acc), ToXPCDocument(doc),
+ domNode, fromUser,
+ state, extra, sc->IsStateEnabled());
+ return xpEvent.forget();
+ }
+
+ if (eventGroup & (1 << AccEvent::eTextChangeEvent)) {
+ AccTextChangeEvent* tc = downcast_accEvent(aEvent);
+ nsString text;
+ tc->GetModifiedText(text);
+ xpEvent = new xpcAccTextChangeEvent(type, ToXPC(acc), ToXPCDocument(doc),
+ domNode, fromUser,
+ tc->GetStartOffset(), tc->GetLength(),
+ tc->IsTextInserted(), text);
+ return xpEvent.forget();
+ }
+
+ if (eventGroup & (1 << AccEvent::eHideEvent)) {
+ AccHideEvent* hideEvent = downcast_accEvent(aEvent);
+ xpEvent = new xpcAccHideEvent(type, ToXPC(acc), ToXPCDocument(doc),
+ domNode, fromUser,
+ ToXPC(hideEvent->TargetParent()),
+ ToXPC(hideEvent->TargetNextSibling()),
+ ToXPC(hideEvent->TargetPrevSibling()));
+ return xpEvent.forget();
+ }
+
+ if (eventGroup & (1 << AccEvent::eCaretMoveEvent)) {
+ AccCaretMoveEvent* cm = downcast_accEvent(aEvent);
+ xpEvent = new xpcAccCaretMoveEvent(type, ToXPC(acc), ToXPCDocument(doc),
+ domNode, fromUser,
+ cm->GetCaretOffset());
+ return xpEvent.forget();
+ }
+
+ if (eventGroup & (1 << AccEvent::eVirtualCursorChangeEvent)) {
+ AccVCChangeEvent* vcc = downcast_accEvent(aEvent);
+ xpEvent = new xpcAccVirtualCursorChangeEvent(type,
+ ToXPC(acc), ToXPCDocument(doc),
+ domNode, fromUser,
+ ToXPC(vcc->OldAccessible()),
+ vcc->OldStartOffset(),
+ vcc->OldEndOffset(),
+ vcc->Reason());
+ return xpEvent.forget();
+ }
+
+ if (eventGroup & (1 << AccEvent::eObjectAttrChangedEvent)) {
+ AccObjectAttrChangedEvent* oac = downcast_accEvent(aEvent);
+ xpEvent = new xpcAccObjectAttributeChangedEvent(type,
+ ToXPC(acc),
+ ToXPCDocument(doc), domNode,
+ fromUser,
+ oac->GetAttribute());
+ return xpEvent.forget();
+ }
+
+ xpEvent = new xpcAccEvent(type, ToXPC(acc), ToXPCDocument(doc), domNode, fromUser);
+ return xpEvent.forget();
+ }
diff --git a/accessible/base/AccEvent.h b/accessible/base/AccEvent.h
new file mode 100644
index 000000000..416224278
--- /dev/null
+++ b/accessible/base/AccEvent.h
@@ -0,0 +1,570 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 _AccEvent_H_
+#define _AccEvent_H_
+
+#include "nsIAccessibleEvent.h"
+
+#include "mozilla/a11y/Accessible.h"
+
+class nsEventShell;
+namespace mozilla {
+
+namespace dom {
+class Selection;
+}
+
+namespace a11y {
+
+class DocAccessible;
+
+// Constants used to point whether the event is from user input.
+enum EIsFromUserInput
+{
+ // eNoUserInput: event is not from user input
+ eNoUserInput = 0,
+ // eFromUserInput: event is from user input
+ eFromUserInput = 1,
+ // eAutoDetect: the value should be obtained from event state manager
+ eAutoDetect = -1
+};
+
+/**
+ * Generic accessible event.
+ */
+class AccEvent
+{
+public:
+
+ // Rule for accessible events.
+ // The rule will be applied when flushing pending events.
+ enum EEventRule {
+ // eAllowDupes : More than one event of the same type is allowed.
+ // This event will always be emitted. This flag is used for events that
+ // don't support coalescence.
+ eAllowDupes,
+
+ // eCoalesceReorder : For reorder events from the same subtree or the same
+ // node, only the umbrella event on the ancestor will be emitted.
+ eCoalesceReorder,
+
+ // eCoalesceOfSameType : For events of the same type, only the newest event
+ // will be processed.
+ eCoalesceOfSameType,
+
+ // eCoalesceSelectionChange: coalescence of selection change events.
+ eCoalesceSelectionChange,
+
+ // eCoalesceStateChange: coalesce state change events.
+ eCoalesceStateChange,
+
+ // eCoalesceTextSelChange: coalescence of text selection change events.
+ eCoalesceTextSelChange,
+
+ // eRemoveDupes : For repeat events, only the newest event in queue
+ // will be emitted.
+ eRemoveDupes,
+
+ // eDoNotEmit : This event is confirmed as a duplicate, do not emit it.
+ eDoNotEmit
+ };
+
+ // Initialize with an accessible.
+ AccEvent(uint32_t aEventType, Accessible* aAccessible,
+ EIsFromUserInput aIsFromUserInput = eAutoDetect,
+ EEventRule aEventRule = eRemoveDupes);
+
+ // AccEvent
+ uint32_t GetEventType() const { return mEventType; }
+ EEventRule GetEventRule() const { return mEventRule; }
+ bool IsFromUserInput() const { return mIsFromUserInput; }
+ EIsFromUserInput FromUserInput() const
+ { return static_cast<EIsFromUserInput>(mIsFromUserInput); }
+
+ Accessible* GetAccessible() const { return mAccessible; }
+ DocAccessible* Document() const { return mAccessible->Document(); }
+
+ /**
+ * Down casting.
+ */
+ enum EventGroup {
+ eGenericEvent,
+ eStateChangeEvent,
+ eTextChangeEvent,
+ eTreeMutationEvent,
+ eMutationEvent,
+ eReorderEvent,
+ eHideEvent,
+ eShowEvent,
+ eCaretMoveEvent,
+ eTextSelChangeEvent,
+ eSelectionChangeEvent,
+ eTableChangeEvent,
+ eVirtualCursorChangeEvent,
+ eObjectAttrChangedEvent
+ };
+
+ static const EventGroup kEventGroup = eGenericEvent;
+ virtual unsigned int GetEventGroups() const
+ {
+ return 1U << eGenericEvent;
+ }
+
+ /**
+ * Reference counting and cycle collection.
+ */
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AccEvent)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(AccEvent)
+
+protected:
+ virtual ~AccEvent() {}
+
+ bool mIsFromUserInput;
+ uint32_t mEventType;
+ EEventRule mEventRule;
+ RefPtr<Accessible> mAccessible;
+
+ friend class EventQueue;
+ friend class EventTree;
+ friend class ::nsEventShell;
+ friend class NotificationController;
+};
+
+
+/**
+ * Accessible state change event.
+ */
+class AccStateChangeEvent: public AccEvent
+{
+public:
+ AccStateChangeEvent(Accessible* aAccessible, uint64_t aState,
+ bool aIsEnabled,
+ EIsFromUserInput aIsFromUserInput = eAutoDetect) :
+ AccEvent(nsIAccessibleEvent::EVENT_STATE_CHANGE, aAccessible,
+ aIsFromUserInput, eCoalesceStateChange),
+ mState(aState), mIsEnabled(aIsEnabled) { }
+
+ AccStateChangeEvent(Accessible* aAccessible, uint64_t aState) :
+ AccEvent(::nsIAccessibleEvent::EVENT_STATE_CHANGE, aAccessible,
+ eAutoDetect, eCoalesceStateChange), mState(aState)
+ { mIsEnabled = (mAccessible->State() & mState) != 0; }
+
+ // AccEvent
+ static const EventGroup kEventGroup = eStateChangeEvent;
+ virtual unsigned int GetEventGroups() const override
+ {
+ return AccEvent::GetEventGroups() | (1U << eStateChangeEvent);
+ }
+
+ // AccStateChangeEvent
+ uint64_t GetState() const { return mState; }
+ bool IsStateEnabled() const { return mIsEnabled; }
+
+private:
+ uint64_t mState;
+ bool mIsEnabled;
+
+ friend class EventQueue;
+};
+
+
+/**
+ * Accessible text change event.
+ */
+class AccTextChangeEvent: public AccEvent
+{
+public:
+ AccTextChangeEvent(Accessible* aAccessible, int32_t aStart,
+ const nsAString& aModifiedText, bool aIsInserted,
+ EIsFromUserInput aIsFromUserInput = eAutoDetect);
+
+ // AccEvent
+ static const EventGroup kEventGroup = eTextChangeEvent;
+ virtual unsigned int GetEventGroups() const override
+ {
+ return AccEvent::GetEventGroups() | (1U << eTextChangeEvent);
+ }
+
+ // AccTextChangeEvent
+ int32_t GetStartOffset() const { return mStart; }
+ uint32_t GetLength() const { return mModifiedText.Length(); }
+ bool IsTextInserted() const { return mIsInserted; }
+ void GetModifiedText(nsAString& aModifiedText)
+ { aModifiedText = mModifiedText; }
+ const nsString& ModifiedText() const { return mModifiedText; }
+
+private:
+ int32_t mStart;
+ bool mIsInserted;
+ nsString mModifiedText;
+
+ friend class EventTree;
+ friend class NotificationController;
+};
+
+/**
+ * A base class for events related to tree mutation, either an AccMutation
+ * event, or an AccReorderEvent.
+ */
+class AccTreeMutationEvent : public AccEvent
+{
+public:
+ AccTreeMutationEvent(uint32_t aEventType, Accessible* aTarget) :
+ AccEvent(aEventType, aTarget, eAutoDetect, eCoalesceReorder), mGeneration(0) {}
+
+ // Event
+ static const EventGroup kEventGroup = eTreeMutationEvent;
+ virtual unsigned int GetEventGroups() const override
+ {
+ return AccEvent::GetEventGroups() | (1U << eTreeMutationEvent);
+ }
+
+ void SetNextEvent(AccTreeMutationEvent* aNext) { mNextEvent = aNext; }
+ void SetPrevEvent(AccTreeMutationEvent* aPrev) { mPrevEvent = aPrev; }
+ AccTreeMutationEvent* NextEvent() const { return mNextEvent; }
+ AccTreeMutationEvent* PrevEvent() const { return mPrevEvent; }
+
+ /**
+ * A sequence number to know when this event was fired.
+ */
+ uint32_t EventGeneration() const { return mGeneration; }
+ void SetEventGeneration(uint32_t aGeneration) { mGeneration = aGeneration; }
+
+private:
+ RefPtr<AccTreeMutationEvent> mNextEvent;
+ RefPtr<AccTreeMutationEvent> mPrevEvent;
+ uint32_t mGeneration;
+};
+
+/**
+ * Base class for show and hide accessible events.
+ */
+class AccMutationEvent: public AccTreeMutationEvent
+{
+public:
+ AccMutationEvent(uint32_t aEventType, Accessible* aTarget) :
+ AccTreeMutationEvent(aEventType, aTarget)
+ {
+ // Don't coalesce these since they are coalesced by reorder event. Coalesce
+ // contained text change events.
+ mParent = mAccessible->Parent();
+ }
+ virtual ~AccMutationEvent() { }
+
+ // Event
+ static const EventGroup kEventGroup = eMutationEvent;
+ virtual unsigned int GetEventGroups() const override
+ {
+ return AccTreeMutationEvent::GetEventGroups() | (1U << eMutationEvent);
+ }
+
+ // MutationEvent
+ bool IsShow() const { return mEventType == nsIAccessibleEvent::EVENT_SHOW; }
+ bool IsHide() const { return mEventType == nsIAccessibleEvent::EVENT_HIDE; }
+
+ Accessible* Parent() const { return mParent; }
+
+protected:
+ nsCOMPtr<nsINode> mNode;
+ RefPtr<Accessible> mParent;
+ RefPtr<AccTextChangeEvent> mTextChangeEvent;
+
+ friend class EventTree;
+ friend class NotificationController;
+};
+
+
+/**
+ * Accessible hide event.
+ */
+class AccHideEvent: public AccMutationEvent
+{
+public:
+ explicit AccHideEvent(Accessible* aTarget, bool aNeedsShutdown = true);
+
+ // Event
+ static const EventGroup kEventGroup = eHideEvent;
+ virtual unsigned int GetEventGroups() const override
+ {
+ return AccMutationEvent::GetEventGroups() | (1U << eHideEvent);
+ }
+
+ // AccHideEvent
+ Accessible* TargetParent() const { return mParent; }
+ Accessible* TargetNextSibling() const { return mNextSibling; }
+ Accessible* TargetPrevSibling() const { return mPrevSibling; }
+ bool NeedsShutdown() const { return mNeedsShutdown; }
+
+protected:
+ bool mNeedsShutdown;
+ RefPtr<Accessible> mNextSibling;
+ RefPtr<Accessible> mPrevSibling;
+
+ friend class EventTree;
+ friend class NotificationController;
+};
+
+
+/**
+ * Accessible show event.
+ */
+class AccShowEvent: public AccMutationEvent
+{
+public:
+ explicit AccShowEvent(Accessible* aTarget);
+
+ // Event
+ static const EventGroup kEventGroup = eShowEvent;
+ virtual unsigned int GetEventGroups() const override
+ {
+ return AccMutationEvent::GetEventGroups() | (1U << eShowEvent);
+ }
+
+ uint32_t InsertionIndex() const { return mInsertionIndex; }
+
+private:
+ nsTArray<RefPtr<AccHideEvent>> mPrecedingEvents;
+ uint32_t mInsertionIndex;
+
+ friend class EventTree;
+};
+
+
+/**
+ * Class for reorder accessible event. Takes care about
+ */
+class AccReorderEvent : public AccTreeMutationEvent
+{
+public:
+ explicit AccReorderEvent(Accessible* aTarget) :
+ AccTreeMutationEvent(::nsIAccessibleEvent::EVENT_REORDER, aTarget) { }
+ virtual ~AccReorderEvent() { }
+
+ // Event
+ static const EventGroup kEventGroup = eReorderEvent;
+ virtual unsigned int GetEventGroups() const override
+ {
+ return AccTreeMutationEvent::GetEventGroups() | (1U << eReorderEvent);
+ }
+};
+
+
+/**
+ * Accessible caret move event.
+ */
+class AccCaretMoveEvent: public AccEvent
+{
+public:
+ AccCaretMoveEvent(Accessible* aAccessible, int32_t aCaretOffset,
+ EIsFromUserInput aIsFromUserInput = eAutoDetect) :
+ AccEvent(::nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED, aAccessible,
+ aIsFromUserInput),
+ mCaretOffset(aCaretOffset) { }
+ virtual ~AccCaretMoveEvent() { }
+
+ // AccEvent
+ static const EventGroup kEventGroup = eCaretMoveEvent;
+ virtual unsigned int GetEventGroups() const override
+ {
+ return AccEvent::GetEventGroups() | (1U << eCaretMoveEvent);
+ }
+
+ // AccCaretMoveEvent
+ int32_t GetCaretOffset() const { return mCaretOffset; }
+
+private:
+ int32_t mCaretOffset;
+};
+
+
+/**
+ * Accessible text selection change event.
+ */
+class AccTextSelChangeEvent : public AccEvent
+{
+public:
+ AccTextSelChangeEvent(HyperTextAccessible* aTarget,
+ dom::Selection* aSelection,
+ int32_t aReason);
+ virtual ~AccTextSelChangeEvent();
+
+ // AccEvent
+ static const EventGroup kEventGroup = eTextSelChangeEvent;
+ virtual unsigned int GetEventGroups() const override
+ {
+ return AccEvent::GetEventGroups() | (1U << eTextSelChangeEvent);
+ }
+
+ // AccTextSelChangeEvent
+
+ /**
+ * Return true if the text selection change wasn't caused by pure caret move.
+ */
+ bool IsCaretMoveOnly() const;
+
+private:
+ RefPtr<dom::Selection> mSel;
+ int32_t mReason;
+
+ friend class EventQueue;
+ friend class SelectionManager;
+};
+
+
+/**
+ * Accessible widget selection change event.
+ */
+class AccSelChangeEvent : public AccEvent
+{
+public:
+ enum SelChangeType {
+ eSelectionAdd,
+ eSelectionRemove
+ };
+
+ AccSelChangeEvent(Accessible* aWidget, Accessible* aItem,
+ SelChangeType aSelChangeType);
+
+ virtual ~AccSelChangeEvent() { }
+
+ // AccEvent
+ static const EventGroup kEventGroup = eSelectionChangeEvent;
+ virtual unsigned int GetEventGroups() const override
+ {
+ return AccEvent::GetEventGroups() | (1U << eSelectionChangeEvent);
+ }
+
+ // AccSelChangeEvent
+ Accessible* Widget() const { return mWidget; }
+
+private:
+ RefPtr<Accessible> mWidget;
+ RefPtr<Accessible> mItem;
+ SelChangeType mSelChangeType;
+ uint32_t mPreceedingCount;
+ AccSelChangeEvent* mPackedEvent;
+
+ friend class EventQueue;
+};
+
+
+/**
+ * Accessible table change event.
+ */
+class AccTableChangeEvent : public AccEvent
+{
+public:
+ AccTableChangeEvent(Accessible* aAccessible, uint32_t aEventType,
+ int32_t aRowOrColIndex, int32_t aNumRowsOrCols);
+
+ // AccEvent
+ static const EventGroup kEventGroup = eTableChangeEvent;
+ virtual unsigned int GetEventGroups() const override
+ {
+ return AccEvent::GetEventGroups() | (1U << eTableChangeEvent);
+ }
+
+ // AccTableChangeEvent
+ uint32_t GetIndex() const { return mRowOrColIndex; }
+ uint32_t GetCount() const { return mNumRowsOrCols; }
+
+private:
+ uint32_t mRowOrColIndex; // the start row/column after which the rows are inserted/deleted.
+ uint32_t mNumRowsOrCols; // the number of inserted/deleted rows/columns
+};
+
+/**
+ * Accessible virtual cursor change event.
+ */
+class AccVCChangeEvent : public AccEvent
+{
+public:
+ AccVCChangeEvent(Accessible* aAccessible,
+ Accessible* aOldAccessible,
+ int32_t aOldStart, int32_t aOldEnd,
+ int16_t aReason,
+ EIsFromUserInput aIsFromUserInput = eFromUserInput);
+
+ virtual ~AccVCChangeEvent() { }
+
+ // AccEvent
+ static const EventGroup kEventGroup = eVirtualCursorChangeEvent;
+ virtual unsigned int GetEventGroups() const override
+ {
+ return AccEvent::GetEventGroups() | (1U << eVirtualCursorChangeEvent);
+ }
+
+ // AccTableChangeEvent
+ Accessible* OldAccessible() const { return mOldAccessible; }
+ int32_t OldStartOffset() const { return mOldStart; }
+ int32_t OldEndOffset() const { return mOldEnd; }
+ int32_t Reason() const { return mReason; }
+
+private:
+ RefPtr<Accessible> mOldAccessible;
+ int32_t mOldStart;
+ int32_t mOldEnd;
+ int16_t mReason;
+};
+
+/**
+ * Accessible object attribute changed event.
+ */
+class AccObjectAttrChangedEvent: public AccEvent
+{
+public:
+ AccObjectAttrChangedEvent(Accessible* aAccessible, nsIAtom* aAttribute) :
+ AccEvent(::nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED, aAccessible),
+ mAttribute(aAttribute) { }
+
+ // AccEvent
+ static const EventGroup kEventGroup = eObjectAttrChangedEvent;
+ virtual unsigned int GetEventGroups() const override
+ {
+ return AccEvent::GetEventGroups() | (1U << eObjectAttrChangedEvent);
+ }
+
+ // AccObjectAttrChangedEvent
+ nsIAtom* GetAttribute() const { return mAttribute; }
+
+private:
+ nsCOMPtr<nsIAtom> mAttribute;
+
+ virtual ~AccObjectAttrChangedEvent() { }
+};
+
+/**
+ * Downcast the generic accessible event object to derived type.
+ */
+class downcast_accEvent
+{
+public:
+ explicit downcast_accEvent(AccEvent* e) : mRawPtr(e) { }
+
+ template<class Destination>
+ operator Destination*() {
+ if (!mRawPtr)
+ return nullptr;
+
+ return mRawPtr->GetEventGroups() & (1U << Destination::kEventGroup) ?
+ static_cast<Destination*>(mRawPtr) : nullptr;
+ }
+
+private:
+ AccEvent* mRawPtr;
+};
+
+/**
+ * Return a new xpcom accessible event for the given internal one.
+ */
+already_AddRefed<nsIAccessibleEvent>
+MakeXPCEvent(AccEvent* aEvent);
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/base/AccGroupInfo.cpp b/accessible/base/AccGroupInfo.cpp
new file mode 100644
index 000000000..bef707887
--- /dev/null
+++ b/accessible/base/AccGroupInfo.cpp
@@ -0,0 +1,227 @@
+/* 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 "AccGroupInfo.h"
+#include "nsAccUtils.h"
+
+#include "Role.h"
+#include "States.h"
+
+using namespace mozilla::a11y;
+
+AccGroupInfo::AccGroupInfo(Accessible* aItem, role aRole) :
+ mPosInSet(0), mSetSize(0), mParent(nullptr), mItem(aItem), mRole(aRole)
+{
+ MOZ_COUNT_CTOR(AccGroupInfo);
+ Update();
+}
+
+void
+AccGroupInfo::Update()
+{
+ Accessible* parent = mItem->Parent();
+ if (!parent)
+ return;
+
+ int32_t indexInParent = mItem->IndexInParent();
+ uint32_t siblingCount = parent->ChildCount();
+ if (indexInParent == -1 ||
+ indexInParent >= static_cast<int32_t>(siblingCount)) {
+ NS_ERROR("Wrong index in parent! Tree invalidation problem.");
+ return;
+ }
+
+ int32_t level = nsAccUtils::GetARIAOrDefaultLevel(mItem);
+
+ // Compute position in set.
+ mPosInSet = 1;
+ for (int32_t idx = indexInParent - 1; idx >= 0 ; idx--) {
+ Accessible* sibling = parent->GetChildAt(idx);
+ roles::Role siblingRole = sibling->Role();
+
+ // If the sibling is separator then the group is ended.
+ if (siblingRole == roles::SEPARATOR)
+ break;
+
+ // If sibling is not visible and hasn't the same base role.
+ if (BaseRole(siblingRole) != mRole || sibling->State() & states::INVISIBLE)
+ continue;
+
+ // Check if it's hierarchical flatten structure, i.e. if the sibling
+ // level is lesser than this one then group is ended, if the sibling level
+ // is greater than this one then the group is split by some child elements
+ // (group will be continued).
+ int32_t siblingLevel = nsAccUtils::GetARIAOrDefaultLevel(sibling);
+ if (siblingLevel < level) {
+ mParent = sibling;
+ break;
+ }
+
+ // Skip subset.
+ if (siblingLevel > level)
+ continue;
+
+ // If the previous item in the group has calculated group information then
+ // build group information for this item based on found one.
+ if (sibling->mBits.groupInfo) {
+ mPosInSet += sibling->mBits.groupInfo->mPosInSet;
+ mParent = sibling->mBits.groupInfo->mParent;
+ mSetSize = sibling->mBits.groupInfo->mSetSize;
+ return;
+ }
+
+ mPosInSet++;
+ }
+
+ // Compute set size.
+ mSetSize = mPosInSet;
+
+ for (uint32_t idx = indexInParent + 1; idx < siblingCount; idx++) {
+ Accessible* sibling = parent->GetChildAt(idx);
+
+ roles::Role siblingRole = sibling->Role();
+
+ // If the sibling is separator then the group is ended.
+ if (siblingRole == roles::SEPARATOR)
+ break;
+
+ // If sibling is visible and has the same base role
+ if (BaseRole(siblingRole) != mRole || sibling->State() & states::INVISIBLE)
+ continue;
+
+ // and check if it's hierarchical flatten structure.
+ int32_t siblingLevel = nsAccUtils::GetARIAOrDefaultLevel(sibling);
+ if (siblingLevel < level)
+ break;
+
+ // Skip subset.
+ if (siblingLevel > level)
+ continue;
+
+ // If the next item in the group has calculated group information then
+ // build group information for this item based on found one.
+ if (sibling->mBits.groupInfo) {
+ mParent = sibling->mBits.groupInfo->mParent;
+ mSetSize = sibling->mBits.groupInfo->mSetSize;
+ return;
+ }
+
+ mSetSize++;
+ }
+
+ if (mParent)
+ return;
+
+ roles::Role parentRole = parent->Role();
+ if (ShouldReportRelations(mRole, parentRole))
+ mParent = parent;
+
+ // ARIA tree and list can be arranged by using ARIA groups to organize levels.
+ if (parentRole != roles::GROUPING)
+ return;
+
+ // Way #1 for ARIA tree (not ARIA treegrid): previous sibling of a group is a
+ // parent. In other words the parent of the tree item will be a group and
+ // the previous tree item of the group is a conceptual parent of the tree
+ // item.
+ if (mRole == roles::OUTLINEITEM) {
+ Accessible* parentPrevSibling = parent->PrevSibling();
+ if (parentPrevSibling && parentPrevSibling->Role() == mRole) {
+ mParent = parentPrevSibling;
+ return;
+ }
+ }
+
+ // Way #2 for ARIA list and tree: group is a child of an item. In other words
+ // the parent of the item will be a group and containing item of the group is
+ // a conceptual parent of the item.
+ if (mRole == roles::LISTITEM || mRole == roles::OUTLINEITEM) {
+ Accessible* grandParent = parent->Parent();
+ if (grandParent && grandParent->Role() == mRole)
+ mParent = grandParent;
+ }
+}
+
+Accessible*
+AccGroupInfo::FirstItemOf(Accessible* aContainer)
+{
+ // ARIA tree can be arranged by ARIA groups case #1 (previous sibling of a
+ // group is a parent) or by aria-level.
+ a11y::role containerRole = aContainer->Role();
+ Accessible* item = aContainer->NextSibling();
+ if (item) {
+ if (containerRole == roles::OUTLINEITEM && item->Role() == roles::GROUPING)
+ item = item->FirstChild();
+
+ if (item) {
+ AccGroupInfo* itemGroupInfo = item->GetGroupInfo();
+ if (itemGroupInfo && itemGroupInfo->ConceptualParent() == aContainer)
+ return item;
+ }
+ }
+
+ // ARIA list and tree can be arranged by ARIA groups case #2 (group is
+ // a child of an item).
+ item = aContainer->LastChild();
+ if (!item)
+ return nullptr;
+
+ if (item->Role() == roles::GROUPING &&
+ (containerRole == roles::LISTITEM || containerRole == roles::OUTLINEITEM)) {
+ item = item->FirstChild();
+ if (item) {
+ AccGroupInfo* itemGroupInfo = item->GetGroupInfo();
+ if (itemGroupInfo && itemGroupInfo->ConceptualParent() == aContainer)
+ return item;
+ }
+ }
+
+ // Otherwise, it can be a direct child if the container is a list or tree.
+ item = aContainer->FirstChild();
+ if (ShouldReportRelations(item->Role(), containerRole))
+ return item;
+
+ return nullptr;
+}
+
+Accessible*
+AccGroupInfo::NextItemTo(Accessible* aItem)
+{
+ AccGroupInfo* groupInfo = aItem->GetGroupInfo();
+ if (!groupInfo)
+ return nullptr;
+
+ // If the item in middle of the group then search next item in siblings.
+ if (groupInfo->PosInSet() >= groupInfo->SetSize())
+ return nullptr;
+
+ Accessible* parent = aItem->Parent();
+ uint32_t childCount = parent->ChildCount();
+ for (uint32_t idx = aItem->IndexInParent() + 1; idx < childCount; idx++) {
+ Accessible* nextItem = parent->GetChildAt(idx);
+ AccGroupInfo* nextGroupInfo = nextItem->GetGroupInfo();
+ if (nextGroupInfo &&
+ nextGroupInfo->ConceptualParent() == groupInfo->ConceptualParent()) {
+ return nextItem;
+ }
+ }
+
+ NS_NOTREACHED("Item in the middle of the group but there's no next item!");
+ return nullptr;
+}
+
+bool
+AccGroupInfo::ShouldReportRelations(role aRole, role aParentRole)
+{
+ // We only want to report hierarchy-based node relations for items in tree or
+ // list form. ARIA level/owns relations are always reported.
+ if (aParentRole == roles::OUTLINE && aRole == roles::OUTLINEITEM)
+ return true;
+ if (aParentRole == roles::TREE_TABLE && aRole == roles::ROW)
+ return true;
+ if (aParentRole == roles::LIST && aRole == roles::LISTITEM)
+ return true;
+
+ return false;
+}
diff --git a/accessible/base/AccGroupInfo.h b/accessible/base/AccGroupInfo.h
new file mode 100644
index 000000000..093258070
--- /dev/null
+++ b/accessible/base/AccGroupInfo.h
@@ -0,0 +1,114 @@
+/* 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 AccGroupInfo_h_
+#define AccGroupInfo_h_
+
+#include "Accessible-inl.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Calculate and store group information.
+ */
+class AccGroupInfo
+{
+public:
+ ~AccGroupInfo() { MOZ_COUNT_DTOR(AccGroupInfo); }
+
+ /**
+ * Return 1-based position in the group.
+ */
+ uint32_t PosInSet() const { return mPosInSet; }
+
+ /**
+ * Return a number of items in the group.
+ */
+ uint32_t SetSize() const { return mSetSize; }
+
+ /**
+ * Return a direct or logical parent of the accessible that this group info is
+ * created for.
+ */
+ Accessible* ConceptualParent() const { return mParent; }
+
+ /**
+ * Update group information.
+ */
+ void Update();
+
+ /**
+ * Create group info.
+ */
+ static AccGroupInfo* CreateGroupInfo(Accessible* aAccessible)
+ {
+ mozilla::a11y::role role = aAccessible->Role();
+ if (role != mozilla::a11y::roles::ROW &&
+ role != mozilla::a11y::roles::OUTLINEITEM &&
+ role != mozilla::a11y::roles::OPTION &&
+ role != mozilla::a11y::roles::LISTITEM &&
+ role != mozilla::a11y::roles::MENUITEM &&
+ role != mozilla::a11y::roles::COMBOBOX_OPTION &&
+ role != mozilla::a11y::roles::RICH_OPTION &&
+ role != mozilla::a11y::roles::CHECK_RICH_OPTION &&
+ role != mozilla::a11y::roles::PARENT_MENUITEM &&
+ role != mozilla::a11y::roles::CHECK_MENU_ITEM &&
+ role != mozilla::a11y::roles::RADIO_MENU_ITEM &&
+ role != mozilla::a11y::roles::RADIOBUTTON &&
+ role != mozilla::a11y::roles::PAGETAB)
+ return nullptr;
+
+ AccGroupInfo* info = new AccGroupInfo(aAccessible, BaseRole(role));
+ return info;
+ }
+
+ /**
+ * Return a first item for the given container.
+ */
+ static Accessible* FirstItemOf(Accessible* aContainer);
+
+ /**
+ * Return next item of the same group to the given item.
+ */
+ static Accessible* NextItemTo(Accessible* aItem);
+
+protected:
+ AccGroupInfo(Accessible* aItem, a11y::role aRole);
+
+private:
+ AccGroupInfo() = delete;
+ AccGroupInfo(const AccGroupInfo&) = delete;
+ AccGroupInfo& operator =(const AccGroupInfo&) = delete;
+
+ static mozilla::a11y::role BaseRole(mozilla::a11y::role aRole)
+ {
+ if (aRole == mozilla::a11y::roles::CHECK_MENU_ITEM ||
+ aRole == mozilla::a11y::roles::PARENT_MENUITEM ||
+ aRole == mozilla::a11y::roles::RADIO_MENU_ITEM)
+ return mozilla::a11y::roles::MENUITEM;
+
+ if (aRole == mozilla::a11y::roles::CHECK_RICH_OPTION)
+ return mozilla::a11y::roles::RICH_OPTION;
+
+ return aRole;
+ }
+
+ /**
+ * Return true if the given parent and child roles should have their node
+ * relations reported.
+ */
+ static bool ShouldReportRelations(a11y::role aRole, a11y::role aParentRole);
+
+ uint32_t mPosInSet;
+ uint32_t mSetSize;
+ Accessible* mParent;
+ Accessible* mItem;
+ a11y::role mRole;
+};
+
+} // namespace mozilla
+} // namespace a11y
+
+#endif
diff --git a/accessible/base/AccIterator.cpp b/accessible/base/AccIterator.cpp
new file mode 100644
index 000000000..f6e890c50
--- /dev/null
+++ b/accessible/base/AccIterator.cpp
@@ -0,0 +1,414 @@
+/* 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 "AccIterator.h"
+
+#include "AccGroupInfo.h"
+#ifdef MOZ_XUL
+#include "XULTreeAccessible.h"
+#endif
+
+#include "mozilla/dom/HTMLLabelElement.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// AccIterator
+////////////////////////////////////////////////////////////////////////////////
+
+AccIterator::AccIterator(Accessible* aAccessible,
+ filters::FilterFuncPtr aFilterFunc) :
+ mFilterFunc(aFilterFunc)
+{
+ mState = new IteratorState(aAccessible);
+}
+
+AccIterator::~AccIterator()
+{
+ while (mState) {
+ IteratorState *tmp = mState;
+ mState = tmp->mParentState;
+ delete tmp;
+ }
+}
+
+Accessible*
+AccIterator::Next()
+{
+ while (mState) {
+ Accessible* child = mState->mParent->GetChildAt(mState->mIndex++);
+ if (!child) {
+ IteratorState* tmp = mState;
+ mState = mState->mParentState;
+ delete tmp;
+
+ continue;
+ }
+
+ uint32_t result = mFilterFunc(child);
+ if (result & filters::eMatch)
+ return child;
+
+ if (!(result & filters::eSkipSubtree)) {
+ IteratorState* childState = new IteratorState(child, mState);
+ mState = childState;
+ }
+ }
+
+ return nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsAccIterator::IteratorState
+
+AccIterator::IteratorState::IteratorState(Accessible* aParent,
+ IteratorState *mParentState) :
+ mParent(aParent), mIndex(0), mParentState(mParentState)
+{
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// RelatedAccIterator
+////////////////////////////////////////////////////////////////////////////////
+
+RelatedAccIterator::
+ RelatedAccIterator(DocAccessible* aDocument, nsIContent* aDependentContent,
+ nsIAtom* aRelAttr) :
+ mDocument(aDocument), mRelAttr(aRelAttr), mProviders(nullptr),
+ mBindingParent(nullptr), mIndex(0)
+{
+ mBindingParent = aDependentContent->GetBindingParent();
+ nsIAtom* IDAttr = mBindingParent ?
+ nsGkAtoms::anonid : nsGkAtoms::id;
+
+ nsAutoString id;
+ if (aDependentContent->GetAttr(kNameSpaceID_None, IDAttr, id))
+ mProviders = mDocument->mDependentIDsHash.Get(id);
+}
+
+Accessible*
+RelatedAccIterator::Next()
+{
+ if (!mProviders)
+ return nullptr;
+
+ while (mIndex < mProviders->Length()) {
+ DocAccessible::AttrRelProvider* provider = (*mProviders)[mIndex++];
+
+ // Return related accessible for the given attribute and if the provider
+ // content is in the same binding in the case of XBL usage.
+ if (provider->mRelAttr == mRelAttr) {
+ nsIContent* bindingParent = provider->mContent->GetBindingParent();
+ bool inScope = mBindingParent == bindingParent ||
+ mBindingParent == provider->mContent;
+
+ if (inScope) {
+ Accessible* related = mDocument->GetAccessible(provider->mContent);
+ if (related)
+ return related;
+
+ // If the document content is pointed by relation then return the document
+ // itself.
+ if (provider->mContent == mDocument->GetContent())
+ return mDocument;
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLLabelIterator
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLLabelIterator::
+ HTMLLabelIterator(DocAccessible* aDocument, const Accessible* aAccessible,
+ LabelFilter aFilter) :
+ mRelIter(aDocument, aAccessible->GetContent(), nsGkAtoms::_for),
+ mAcc(aAccessible), mLabelFilter(aFilter)
+{
+}
+
+bool
+HTMLLabelIterator::IsLabel(Accessible* aLabel)
+{
+ dom::HTMLLabelElement* labelEl =
+ dom::HTMLLabelElement::FromContent(aLabel->GetContent());
+ return labelEl && labelEl->GetControl() == mAcc->GetContent();
+}
+
+Accessible*
+HTMLLabelIterator::Next()
+{
+ // Get either <label for="[id]"> element which explicitly points to given
+ // element, or <label> ancestor which implicitly point to it.
+ Accessible* label = nullptr;
+ while ((label = mRelIter.Next())) {
+ if (IsLabel(label)) {
+ return label;
+ }
+ }
+
+ // Ignore ancestor label on not widget accessible.
+ if (mLabelFilter == eSkipAncestorLabel || !mAcc->IsWidget())
+ return nullptr;
+
+ // Go up tree to get a name of ancestor label if there is one (an ancestor
+ // <label> implicitly points to us). Don't go up farther than form or
+ // document.
+ Accessible* walkUp = mAcc->Parent();
+ while (walkUp && !walkUp->IsDoc()) {
+ nsIContent* walkUpEl = walkUp->GetContent();
+ if (IsLabel(walkUp) &&
+ !walkUpEl->HasAttr(kNameSpaceID_None, nsGkAtoms::_for)) {
+ mLabelFilter = eSkipAncestorLabel; // prevent infinite loop
+ return walkUp;
+ }
+
+ if (walkUpEl->IsHTMLElement(nsGkAtoms::form))
+ break;
+
+ walkUp = walkUp->Parent();
+ }
+
+ return nullptr;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLOutputIterator
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLOutputIterator::
+HTMLOutputIterator(DocAccessible* aDocument, nsIContent* aElement) :
+ mRelIter(aDocument, aElement, nsGkAtoms::_for)
+{
+}
+
+Accessible*
+HTMLOutputIterator::Next()
+{
+ Accessible* output = nullptr;
+ while ((output = mRelIter.Next())) {
+ if (output->GetContent()->IsHTMLElement(nsGkAtoms::output))
+ return output;
+ }
+
+ return nullptr;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// XULLabelIterator
+////////////////////////////////////////////////////////////////////////////////
+
+XULLabelIterator::
+ XULLabelIterator(DocAccessible* aDocument, nsIContent* aElement) :
+ mRelIter(aDocument, aElement, nsGkAtoms::control)
+{
+}
+
+Accessible*
+XULLabelIterator::Next()
+{
+ Accessible* label = nullptr;
+ while ((label = mRelIter.Next())) {
+ if (label->GetContent()->IsXULElement(nsGkAtoms::label))
+ return label;
+ }
+
+ return nullptr;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// XULDescriptionIterator
+////////////////////////////////////////////////////////////////////////////////
+
+XULDescriptionIterator::
+ XULDescriptionIterator(DocAccessible* aDocument, nsIContent* aElement) :
+ mRelIter(aDocument, aElement, nsGkAtoms::control)
+{
+}
+
+Accessible*
+XULDescriptionIterator::Next()
+{
+ Accessible* descr = nullptr;
+ while ((descr = mRelIter.Next())) {
+ if (descr->GetContent()->IsXULElement(nsGkAtoms::description))
+ return descr;
+ }
+
+ return nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// IDRefsIterator
+////////////////////////////////////////////////////////////////////////////////
+
+IDRefsIterator::
+ IDRefsIterator(DocAccessible* aDoc, nsIContent* aContent,
+ nsIAtom* aIDRefsAttr) :
+ mContent(aContent), mDoc(aDoc), mCurrIdx(0)
+{
+ if (mContent->IsInUncomposedDoc())
+ mContent->GetAttr(kNameSpaceID_None, aIDRefsAttr, mIDs);
+}
+
+const nsDependentSubstring
+IDRefsIterator::NextID()
+{
+ for (; mCurrIdx < mIDs.Length(); mCurrIdx++) {
+ if (!NS_IsAsciiWhitespace(mIDs[mCurrIdx]))
+ break;
+ }
+
+ if (mCurrIdx >= mIDs.Length())
+ return nsDependentSubstring();
+
+ nsAString::index_type idStartIdx = mCurrIdx;
+ while (++mCurrIdx < mIDs.Length()) {
+ if (NS_IsAsciiWhitespace(mIDs[mCurrIdx]))
+ break;
+ }
+
+ return Substring(mIDs, idStartIdx, mCurrIdx++ - idStartIdx);
+}
+
+nsIContent*
+IDRefsIterator::NextElem()
+{
+ while (true) {
+ const nsDependentSubstring id = NextID();
+ if (id.IsEmpty())
+ break;
+
+ nsIContent* refContent = GetElem(id);
+ if (refContent)
+ return refContent;
+ }
+
+ return nullptr;
+}
+
+nsIContent*
+IDRefsIterator::GetElem(const nsDependentSubstring& aID)
+{
+ // Get elements in DOM tree by ID attribute if this is an explicit content.
+ // In case of bound element check its anonymous subtree.
+ if (!mContent->IsInAnonymousSubtree()) {
+ dom::Element* refElm = mContent->OwnerDoc()->GetElementById(aID);
+ if (refElm || !mContent->GetXBLBinding())
+ return refElm;
+ }
+
+ // If content is in anonymous subtree or an element having anonymous subtree
+ // then use "anonid" attribute to get elements in anonymous subtree.
+
+ // Check inside the binding the element is contained in.
+ nsIContent* bindingParent = mContent->GetBindingParent();
+ if (bindingParent) {
+ nsIContent* refElm = bindingParent->OwnerDoc()->
+ GetAnonymousElementByAttribute(bindingParent, nsGkAtoms::anonid, aID);
+
+ if (refElm)
+ return refElm;
+ }
+
+ // Check inside the binding of the element.
+ if (mContent->GetXBLBinding()) {
+ return mContent->OwnerDoc()->
+ GetAnonymousElementByAttribute(mContent, nsGkAtoms::anonid, aID);
+ }
+
+ return nullptr;
+}
+
+Accessible*
+IDRefsIterator::Next()
+{
+ nsIContent* nextEl = nullptr;
+ while ((nextEl = NextElem())) {
+ Accessible* acc = mDoc->GetAccessible(nextEl);
+ if (acc) {
+ return acc;
+ }
+ }
+ return nullptr;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// SingleAccIterator
+////////////////////////////////////////////////////////////////////////////////
+
+Accessible*
+SingleAccIterator::Next()
+{
+ RefPtr<Accessible> nextAcc;
+ mAcc.swap(nextAcc);
+ if (!nextAcc || nextAcc->IsDefunct()) {
+ return nullptr;
+ }
+ return nextAcc;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// ItemIterator
+////////////////////////////////////////////////////////////////////////////////
+
+Accessible*
+ItemIterator::Next()
+{
+ if (mContainer) {
+ mAnchor = AccGroupInfo::FirstItemOf(mContainer);
+ mContainer = nullptr;
+ return mAnchor;
+ }
+
+ return mAnchor ? (mAnchor = AccGroupInfo::NextItemTo(mAnchor)) : nullptr;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeItemIterator
+////////////////////////////////////////////////////////////////////////////////
+
+XULTreeItemIterator::XULTreeItemIterator(XULTreeAccessible* aXULTree,
+ nsITreeView* aTreeView,
+ int32_t aRowIdx) :
+ mXULTree(aXULTree), mTreeView(aTreeView), mRowCount(-1),
+ mContainerLevel(-1), mCurrRowIdx(aRowIdx + 1)
+{
+ mTreeView->GetRowCount(&mRowCount);
+ if (aRowIdx != -1)
+ mTreeView->GetLevel(aRowIdx, &mContainerLevel);
+}
+
+Accessible*
+XULTreeItemIterator::Next()
+{
+ while (mCurrRowIdx < mRowCount) {
+ int32_t level = 0;
+ mTreeView->GetLevel(mCurrRowIdx, &level);
+
+ if (level == mContainerLevel + 1)
+ return mXULTree->GetTreeItemAccessible(mCurrRowIdx++);
+
+ if (level <= mContainerLevel) { // got level up
+ mCurrRowIdx = mRowCount;
+ break;
+ }
+
+ mCurrRowIdx++;
+ }
+
+ return nullptr;
+}
diff --git a/accessible/base/AccIterator.h b/accessible/base/AccIterator.h
new file mode 100644
index 000000000..362eb3a92
--- /dev/null
+++ b/accessible/base/AccIterator.h
@@ -0,0 +1,325 @@
+/* -*- 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_AccIterator_h__
+#define mozilla_a11y_AccIterator_h__
+
+#include "DocAccessible.h"
+#include "Filters.h"
+
+#include <memory>
+
+class nsITreeView;
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * AccIterable is a basic interface for iterators over accessibles.
+ */
+class AccIterable
+{
+public:
+ virtual ~AccIterable() { }
+ virtual Accessible* Next() = 0;
+
+private:
+ friend class Relation;
+ std::unique_ptr<AccIterable> mNextIter;
+};
+
+/**
+ * Allows to iterate through accessible children or subtree complying with
+ * filter function.
+ */
+class AccIterator : public AccIterable
+{
+public:
+ AccIterator(Accessible* aRoot, filters::FilterFuncPtr aFilterFunc);
+ virtual ~AccIterator();
+
+ /**
+ * Return next accessible complying with filter function. Return the first
+ * accessible for the first time.
+ */
+ virtual Accessible* Next() override;
+
+private:
+ AccIterator();
+ AccIterator(const AccIterator&);
+ AccIterator& operator =(const AccIterator&);
+
+ struct IteratorState
+ {
+ explicit IteratorState(Accessible* aParent, IteratorState* mParentState = nullptr);
+
+ Accessible* mParent;
+ int32_t mIndex;
+ IteratorState* mParentState;
+ };
+
+ filters::FilterFuncPtr mFilterFunc;
+ IteratorState* mState;
+};
+
+
+/**
+ * Allows to traverse through related accessibles that are pointing to the given
+ * dependent accessible by relation attribute.
+ */
+class RelatedAccIterator : public AccIterable
+{
+public:
+ /**
+ * Constructor.
+ *
+ * @param aDocument [in] the document accessible the related
+ * & accessibles belong to.
+ * @param aDependentContent [in] the content of dependent accessible that
+ * relations were requested for
+ * @param aRelAttr [in] relation attribute that relations are
+ * pointed by
+ */
+ RelatedAccIterator(DocAccessible* aDocument, nsIContent* aDependentContent,
+ nsIAtom* aRelAttr);
+
+ virtual ~RelatedAccIterator() { }
+
+ /**
+ * Return next related accessible for the given dependent accessible.
+ */
+ virtual Accessible* Next() override;
+
+private:
+ RelatedAccIterator();
+ RelatedAccIterator(const RelatedAccIterator&);
+ RelatedAccIterator& operator = (const RelatedAccIterator&);
+
+ DocAccessible* mDocument;
+ nsIAtom* mRelAttr;
+ DocAccessible::AttrRelProviderArray* mProviders;
+ nsIContent* mBindingParent;
+ uint32_t mIndex;
+};
+
+
+/**
+ * Used to iterate through HTML labels associated with the given accessible.
+ */
+class HTMLLabelIterator : public AccIterable
+{
+public:
+ enum LabelFilter {
+ eAllLabels,
+ eSkipAncestorLabel
+ };
+
+ HTMLLabelIterator(DocAccessible* aDocument, const Accessible* aAccessible,
+ LabelFilter aFilter = eAllLabels);
+
+ virtual ~HTMLLabelIterator() { }
+
+ /**
+ * Return next label accessible associated with the given element.
+ */
+ virtual Accessible* Next() override;
+
+private:
+ HTMLLabelIterator();
+ HTMLLabelIterator(const HTMLLabelIterator&);
+ HTMLLabelIterator& operator = (const HTMLLabelIterator&);
+
+ bool IsLabel(Accessible* aLabel);
+
+ RelatedAccIterator mRelIter;
+ // XXX: replace it on weak reference (bug 678429), it's safe to use raw
+ // pointer now because iterators life cycle is short.
+ const Accessible* mAcc;
+ LabelFilter mLabelFilter;
+};
+
+
+/**
+ * Used to iterate through HTML outputs associated with the given element.
+ */
+class HTMLOutputIterator : public AccIterable
+{
+public:
+ HTMLOutputIterator(DocAccessible* aDocument, nsIContent* aElement);
+ virtual ~HTMLOutputIterator() { }
+
+ /**
+ * Return next output accessible associated with the given element.
+ */
+ virtual Accessible* Next() override;
+
+private:
+ HTMLOutputIterator();
+ HTMLOutputIterator(const HTMLOutputIterator&);
+ HTMLOutputIterator& operator = (const HTMLOutputIterator&);
+
+ RelatedAccIterator mRelIter;
+};
+
+
+/**
+ * Used to iterate through XUL labels associated with the given element.
+ */
+class XULLabelIterator : public AccIterable
+{
+public:
+ XULLabelIterator(DocAccessible* aDocument, nsIContent* aElement);
+ virtual ~XULLabelIterator() { }
+
+ /**
+ * Return next label accessible associated with the given element.
+ */
+ virtual Accessible* Next() override;
+
+private:
+ XULLabelIterator();
+ XULLabelIterator(const XULLabelIterator&);
+ XULLabelIterator& operator = (const XULLabelIterator&);
+
+ RelatedAccIterator mRelIter;
+};
+
+
+/**
+ * Used to iterate through XUL descriptions associated with the given element.
+ */
+class XULDescriptionIterator : public AccIterable
+{
+public:
+ XULDescriptionIterator(DocAccessible* aDocument, nsIContent* aElement);
+ virtual ~XULDescriptionIterator() { }
+
+ /**
+ * Return next description accessible associated with the given element.
+ */
+ virtual Accessible* Next() override;
+
+private:
+ XULDescriptionIterator();
+ XULDescriptionIterator(const XULDescriptionIterator&);
+ XULDescriptionIterator& operator = (const XULDescriptionIterator&);
+
+ RelatedAccIterator mRelIter;
+};
+
+/**
+ * Used to iterate through IDs, elements or accessibles pointed by IDRefs
+ * attribute. Note, any method used to iterate through IDs, elements, or
+ * accessibles moves iterator to next position.
+ */
+class IDRefsIterator : public AccIterable
+{
+public:
+ IDRefsIterator(DocAccessible* aDoc, nsIContent* aContent,
+ nsIAtom* aIDRefsAttr);
+ virtual ~IDRefsIterator() { }
+
+ /**
+ * Return next ID.
+ */
+ const nsDependentSubstring NextID();
+
+ /**
+ * Return next element.
+ */
+ nsIContent* NextElem();
+
+ /**
+ * Return the element with the given ID.
+ */
+ nsIContent* GetElem(const nsDependentSubstring& aID);
+
+ // AccIterable
+ virtual Accessible* Next() override;
+
+private:
+ IDRefsIterator();
+ IDRefsIterator(const IDRefsIterator&);
+ IDRefsIterator operator = (const IDRefsIterator&);
+
+ nsString mIDs;
+ nsIContent* mContent;
+ DocAccessible* mDoc;
+ nsAString::index_type mCurrIdx;
+};
+
+
+/**
+ * Iterator that points to a single accessible returning it on the first call
+ * to Next().
+ */
+class SingleAccIterator : public AccIterable
+{
+public:
+ explicit SingleAccIterator(Accessible* aTarget): mAcc(aTarget) { }
+ virtual ~SingleAccIterator() { }
+
+ virtual Accessible* Next() override;
+
+private:
+ SingleAccIterator();
+ SingleAccIterator(const SingleAccIterator&);
+ SingleAccIterator& operator = (const SingleAccIterator&);
+
+ RefPtr<Accessible> mAcc;
+};
+
+
+/**
+ * Used to iterate items of the given item container.
+ */
+class ItemIterator : public AccIterable
+{
+public:
+ explicit ItemIterator(Accessible* aItemContainer) :
+ mContainer(aItemContainer), mAnchor(nullptr) { }
+ virtual ~ItemIterator() { }
+
+ virtual Accessible* Next() override;
+
+private:
+ ItemIterator() = delete;
+ ItemIterator(const ItemIterator&) = delete;
+ ItemIterator& operator = (const ItemIterator&) = delete;
+
+ Accessible* mContainer;
+ Accessible* mAnchor;
+};
+
+
+/**
+ * Used to iterate through XUL tree items of the same level.
+ */
+class XULTreeItemIterator : public AccIterable
+{
+public:
+ XULTreeItemIterator(XULTreeAccessible* aXULTree, nsITreeView* aTreeView,
+ int32_t aRowIdx);
+ virtual ~XULTreeItemIterator() { }
+
+ virtual Accessible* Next() override;
+
+private:
+ XULTreeItemIterator() = delete;
+ XULTreeItemIterator(const XULTreeItemIterator&) = delete;
+ XULTreeItemIterator& operator = (const XULTreeItemIterator&) = delete;
+
+ XULTreeAccessible* mXULTree;
+ nsITreeView* mTreeView;
+ int32_t mRowCount;
+ int32_t mContainerLevel;
+ int32_t mCurrRowIdx;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/AccTypes.h b/accessible/base/AccTypes.h
new file mode 100644
index 000000000..856d6978e
--- /dev/null
+++ b/accessible/base/AccTypes.h
@@ -0,0 +1,94 @@
+/* -*- 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_AccTypes_h
+#define mozilla_a11y_AccTypes_h
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Accessible object types. Each accessible class can have own type.
+ */
+enum AccType {
+ /**
+ * This set of types is used for accessible creation, keep them together in
+ * alphabetical order since they are used in switch statement.
+ */
+ eNoType,
+ eHTMLBRType,
+ eHTMLButtonType,
+ eHTMLCanvasType,
+ eHTMLCaptionType,
+ eHTMLCheckboxType,
+ eHTMLComboboxType,
+ eHTMLFileInputType,
+ eHTMLGroupboxType,
+ eHTMLHRType,
+ eHTMLImageMapType,
+ eHTMLLiType,
+ eHTMLSelectListType,
+ eHTMLMediaType,
+ eHTMLRadioButtonType,
+ eHTMLRangeType,
+ eHTMLSpinnerType,
+ eHTMLTableType,
+ eHTMLTableCellType,
+ eHTMLTableRowType,
+ eHTMLTextFieldType,
+ eHyperTextType,
+ eImageType,
+ eOuterDocType,
+ ePluginType,
+ eTextLeafType,
+
+ /**
+ * Other accessible types.
+ */
+ eApplicationType,
+ eHTMLOptGroupType,
+ eImageMapType,
+ eMenuPopupType,
+ eProxyType,
+ eProgressType,
+ eRootType,
+ eXULLabelType,
+ eXULListItemType,
+ eXULTabpanelsType,
+ eXULTreeType,
+
+ eLastAccType = eXULTreeType
+};
+
+/**
+ * Generic accessible type, different accessible classes can share the same
+ * type, the same accessible class can have several types.
+ */
+enum AccGenericType {
+ eAlert = 1 << 0,
+ eAutoComplete = 1 << 1,
+ eAutoCompletePopup = 1 << 2,
+ eButton = 1 << 3,
+ eCombobox = 1 << 4,
+ eDocument = 1 << 5,
+ eHyperText = 1 << 6,
+ eLandmark = 1 << 7,
+ eList = 1 << 8,
+ eListControl = 1 << 9,
+ eMenuButton = 1 << 10,
+ eSelect = 1 << 11,
+ eTable = 1 << 12,
+ eTableCell = 1 << 13,
+ eTableRow = 1 << 14,
+ eText = 1 << 15,
+
+ eLastAccGenericType = eText
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_AccTypes_h
diff --git a/accessible/base/AccessibleOrProxy.cpp b/accessible/base/AccessibleOrProxy.cpp
new file mode 100644
index 000000000..77fb44a11
--- /dev/null
+++ b/accessible/base/AccessibleOrProxy.cpp
@@ -0,0 +1,27 @@
+/* -*- 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 "AccessibleOrProxy.h"
+
+AccessibleOrProxy
+AccessibleOrProxy::Parent() const
+{
+ if (IsAccessible()) {
+ return AsAccessible()->Parent();
+ }
+
+ ProxyAccessible* proxy = AsProxy();
+ if (!proxy) {
+ return nullptr;
+ }
+
+ if (ProxyAccessible* parent = proxy->Parent()) {
+ return parent;
+ }
+
+ // Otherwise this should be the proxy for the tab's top level document.
+ return proxy->OuterDocOfRemoteBrowser();
+}
diff --git a/accessible/base/AccessibleOrProxy.h b/accessible/base/AccessibleOrProxy.h
new file mode 100644
index 000000000..0cdf825ca
--- /dev/null
+++ b/accessible/base/AccessibleOrProxy.h
@@ -0,0 +1,123 @@
+/* -*- 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_AccessibleOrProxy_h
+#define mozilla_a11y_AccessibleOrProxy_h
+
+#include "mozilla/a11y/Accessible.h"
+#include "mozilla/a11y/ProxyAccessible.h"
+#include "mozilla/a11y/Role.h"
+
+#include <stdint.h>
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * This class stores an Accessible* or a ProxyAccessible* in a safe manner
+ * with size sizeof(void*).
+ */
+class AccessibleOrProxy
+{
+public:
+ MOZ_IMPLICIT AccessibleOrProxy(Accessible* aAcc) :
+ mBits(reinterpret_cast<uintptr_t>(aAcc)) {}
+ MOZ_IMPLICIT AccessibleOrProxy(ProxyAccessible* aProxy) :
+ mBits(aProxy ? (reinterpret_cast<uintptr_t>(aProxy) | IS_PROXY) : 0) {}
+ MOZ_IMPLICIT AccessibleOrProxy(decltype(nullptr)) : mBits(0) {}
+
+ bool IsProxy() const { return mBits & IS_PROXY; }
+ ProxyAccessible* AsProxy() const
+ {
+ if (IsProxy()) {
+ return reinterpret_cast<ProxyAccessible*>(mBits & ~IS_PROXY);
+ }
+
+ return nullptr;
+ }
+
+ bool IsAccessible() const { return !IsProxy(); }
+ Accessible* AsAccessible() const
+ {
+ if (IsAccessible()) {
+ return reinterpret_cast<Accessible*>(mBits);
+ }
+
+ return nullptr;
+ }
+
+ bool IsNull() const { return mBits == 0; }
+
+ uint32_t ChildCount() const
+ {
+ if (IsProxy()) {
+ return AsProxy()->ChildrenCount();
+ }
+
+ return AsAccessible()->ChildCount();
+ }
+
+ /**
+ * Return the child object either an accessible or a proxied accessible at
+ * the given index.
+ */
+ AccessibleOrProxy ChildAt(uint32_t aIdx)
+ {
+ if (IsProxy()) {
+ return AsProxy()->ChildAt(aIdx);
+ }
+
+ return AsAccessible()->GetChildAt(aIdx);
+ }
+
+ /**
+ * Return the first child object.
+ */
+ AccessibleOrProxy FirstChild()
+ {
+ if (IsProxy()) {
+ return AsProxy()->FirstChild();
+ }
+
+ return AsAccessible()->FirstChild();
+ }
+
+ /**
+ * Return the first child object.
+ */
+ AccessibleOrProxy LastChild()
+ {
+ if (IsProxy()) {
+ return AsProxy()->LastChild();
+ }
+
+ return AsAccessible()->LastChild();
+ }
+
+ role Role() const
+ {
+ if (IsProxy()) {
+ return AsProxy()->Role();
+ }
+
+ return AsAccessible()->Role();
+ }
+
+ AccessibleOrProxy Parent() const;
+
+ // XXX these are implementation details that ideally would not be exposed.
+ uintptr_t Bits() const { return mBits; }
+ void SetBits(uintptr_t aBits) { mBits = aBits; }
+
+private:
+ uintptr_t mBits;
+ static const uintptr_t IS_PROXY = 0x1;
+};
+
+}
+}
+
+#endif
diff --git a/accessible/base/Asserts.cpp b/accessible/base/Asserts.cpp
new file mode 100644
index 000000000..b97a48ec4
--- /dev/null
+++ b/accessible/base/Asserts.cpp
@@ -0,0 +1,26 @@
+/* -*- 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 "nsIAccessibleRelation.h"
+#include "nsIAccessibleRole.h"
+#include "RelationType.h"
+#include "Role.h"
+
+using namespace mozilla::a11y;
+
+#define ROLE(geckoRole, stringRole, atkRole, macRole, msaaRole, ia2Role, nameRule) \
+ static_assert(static_cast<uint32_t>(roles::geckoRole) \
+ == static_cast<uint32_t>(nsIAccessibleRole::ROLE_ ## geckoRole), \
+ "internal and xpcom roles differ!");
+#include "RoleMap.h"
+#undef ROLE
+
+#define RELATIONTYPE(geckoType, stringType, atkType, msaaType, ia2Type) \
+ static_assert(static_cast<uint32_t>(RelationType::geckoType) \
+ == static_cast<uint32_t>(nsIAccessibleRelation::RELATION_ ## geckoType), \
+ "internal and xpcom relations differ!");
+#include "RelationTypeMap.h"
+#undef RELATIONTYPE
diff --git a/accessible/base/DocManager.cpp b/accessible/base/DocManager.cpp
new file mode 100644
index 000000000..786ccbbe3
--- /dev/null
+++ b/accessible/base/DocManager.cpp
@@ -0,0 +1,594 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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 "DocManager.h"
+
+#include "ApplicationAccessible.h"
+#include "ARIAMap.h"
+#include "DocAccessible-inl.h"
+#include "DocAccessibleChild.h"
+#include "DocAccessibleParent.h"
+#include "nsAccessibilityService.h"
+#include "Platform.h"
+#include "RootAccessibleWrap.h"
+#include "xpcAccessibleDocument.h"
+
+#ifdef A11Y_LOG
+#include "Logging.h"
+#endif
+
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
+#include "nsCURILoader.h"
+#include "nsDocShellLoadTypes.h"
+#include "nsIChannel.h"
+#include "nsIDOMDocument.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIWebNavigation.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIWebProgress.h"
+#include "nsCoreUtils.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/dom/TabChild.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+using namespace mozilla::dom;
+
+StaticAutoPtr<nsTArray<DocAccessibleParent*>> DocManager::sRemoteDocuments;
+nsRefPtrHashtable<nsPtrHashKey<const DocAccessibleParent>, xpcAccessibleDocument>*
+DocManager::sRemoteXPCDocumentCache = nullptr;
+
+////////////////////////////////////////////////////////////////////////////////
+// DocManager
+////////////////////////////////////////////////////////////////////////////////
+
+DocManager::DocManager()
+ : mDocAccessibleCache(2), mXPCDocumentCache(0)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DocManager public
+
+DocAccessible*
+DocManager::GetDocAccessible(nsIDocument* aDocument)
+{
+ if (!aDocument)
+ return nullptr;
+
+ DocAccessible* docAcc = GetExistingDocAccessible(aDocument);
+ if (docAcc)
+ return docAcc;
+
+ return CreateDocOrRootAccessible(aDocument);
+}
+
+Accessible*
+DocManager::FindAccessibleInCache(nsINode* aNode) const
+{
+ for (auto iter = mDocAccessibleCache.ConstIter(); !iter.Done(); iter.Next()) {
+ DocAccessible* docAccessible = iter.UserData();
+ NS_ASSERTION(docAccessible,
+ "No doc accessible for the object in doc accessible cache!");
+
+ if (docAccessible) {
+ Accessible* accessible = docAccessible->GetAccessible(aNode);
+ if (accessible) {
+ return accessible;
+ }
+ }
+ }
+ return nullptr;
+}
+
+void
+DocManager::NotifyOfDocumentShutdown(DocAccessible* aDocument,
+ nsIDocument* aDOMDocument)
+{
+ // We need to remove listeners in both cases, when document is being shutdown
+ // or when accessibility service is being shut down as well.
+ RemoveListeners(aDOMDocument);
+
+ // Document will already be removed when accessibility service is shutting
+ // down so we do not need to remove it twice.
+ if (nsAccessibilityService::IsShutdown()) {
+ return;
+ }
+
+ xpcAccessibleDocument* xpcDoc = mXPCDocumentCache.GetWeak(aDocument);
+ if (xpcDoc) {
+ xpcDoc->Shutdown();
+ mXPCDocumentCache.Remove(aDocument);
+ }
+
+ mDocAccessibleCache.Remove(aDOMDocument);
+}
+
+void
+DocManager::NotifyOfRemoteDocShutdown(DocAccessibleParent* aDoc)
+{
+ xpcAccessibleDocument* doc = GetCachedXPCDocument(aDoc);
+ if (doc) {
+ doc->Shutdown();
+ sRemoteXPCDocumentCache->Remove(aDoc);
+ }
+}
+
+xpcAccessibleDocument*
+DocManager::GetXPCDocument(DocAccessible* aDocument)
+{
+ if (!aDocument)
+ return nullptr;
+
+ xpcAccessibleDocument* xpcDoc = mXPCDocumentCache.GetWeak(aDocument);
+ if (!xpcDoc) {
+ xpcDoc = new xpcAccessibleDocument(aDocument);
+ mXPCDocumentCache.Put(aDocument, xpcDoc);
+ }
+ return xpcDoc;
+}
+
+xpcAccessibleDocument*
+DocManager::GetXPCDocument(DocAccessibleParent* aDoc)
+{
+ xpcAccessibleDocument* doc = GetCachedXPCDocument(aDoc);
+ if (doc) {
+ return doc;
+ }
+
+ if (!sRemoteXPCDocumentCache) {
+ sRemoteXPCDocumentCache =
+ new nsRefPtrHashtable<nsPtrHashKey<const DocAccessibleParent>, xpcAccessibleDocument>;
+ }
+
+ doc =
+ new xpcAccessibleDocument(aDoc, Interfaces::DOCUMENT | Interfaces::HYPERTEXT);
+ sRemoteXPCDocumentCache->Put(aDoc, doc);
+
+ return doc;
+}
+
+#ifdef DEBUG
+bool
+DocManager::IsProcessingRefreshDriverNotification() const
+{
+ for (auto iter = mDocAccessibleCache.ConstIter(); !iter.Done(); iter.Next()) {
+ DocAccessible* docAccessible = iter.UserData();
+ NS_ASSERTION(docAccessible,
+ "No doc accessible for the object in doc accessible cache!");
+
+ if (docAccessible && docAccessible->mNotificationController &&
+ docAccessible->mNotificationController->IsUpdating()) {
+ return true;
+ }
+ }
+ return false;
+}
+#endif
+
+
+////////////////////////////////////////////////////////////////////////////////
+// DocManager protected
+
+bool
+DocManager::Init()
+{
+ nsCOMPtr<nsIWebProgress> progress =
+ do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
+
+ if (!progress)
+ return false;
+
+ progress->AddProgressListener(static_cast<nsIWebProgressListener*>(this),
+ nsIWebProgress::NOTIFY_STATE_DOCUMENT);
+
+ return true;
+}
+
+void
+DocManager::Shutdown()
+{
+ nsCOMPtr<nsIWebProgress> progress =
+ do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
+
+ if (progress)
+ progress->RemoveProgressListener(static_cast<nsIWebProgressListener*>(this));
+
+ ClearDocCache();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports
+
+NS_IMPL_ISUPPORTS(DocManager,
+ nsIWebProgressListener,
+ nsIDOMEventListener,
+ nsISupportsWeakReference)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIWebProgressListener
+
+NS_IMETHODIMP
+DocManager::OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, uint32_t aStateFlags,
+ nsresult aStatus)
+{
+ NS_ASSERTION(aStateFlags & STATE_IS_DOCUMENT, "Other notifications excluded");
+
+ if (nsAccessibilityService::IsShutdown() || !aWebProgress ||
+ (aStateFlags & (STATE_START | STATE_STOP)) == 0)
+ return NS_OK;
+
+ nsCOMPtr<mozIDOMWindowProxy> DOMWindow;
+ aWebProgress->GetDOMWindow(getter_AddRefs(DOMWindow));
+ NS_ENSURE_STATE(DOMWindow);
+
+ nsPIDOMWindowOuter* piWindow = nsPIDOMWindowOuter::From(DOMWindow);
+ MOZ_ASSERT(piWindow);
+
+ nsCOMPtr<nsIDocument> document = piWindow->GetDoc();
+ NS_ENSURE_STATE(document);
+
+ // Document was loaded.
+ if (aStateFlags & STATE_STOP) {
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocLoad))
+ logging::DocLoad("document loaded", aWebProgress, aRequest, aStateFlags);
+#endif
+
+ // Figure out an event type to notify the document has been loaded.
+ uint32_t eventType = nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED;
+
+ // Some XUL documents get start state and then stop state with failure
+ // status when everything is ok. Fire document load complete event in this
+ // case.
+ if (NS_SUCCEEDED(aStatus) || !nsCoreUtils::IsContentDocument(document))
+ eventType = nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE;
+
+ // If end consumer has been retargeted for loaded content then do not fire
+ // any event because it means no new document has been loaded, for example,
+ // it happens when user clicks on file link.
+ if (aRequest) {
+ uint32_t loadFlags = 0;
+ aRequest->GetLoadFlags(&loadFlags);
+ if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI)
+ eventType = 0;
+ }
+
+ HandleDOMDocumentLoad(document, eventType);
+ return NS_OK;
+ }
+
+ // Document loading was started.
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocLoad))
+ logging::DocLoad("start document loading", aWebProgress, aRequest, aStateFlags);
+#endif
+
+ DocAccessible* docAcc = GetExistingDocAccessible(document);
+ if (!docAcc)
+ return NS_OK;
+
+ nsCOMPtr<nsIWebNavigation> webNav(do_GetInterface(DOMWindow));
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(webNav));
+ NS_ENSURE_STATE(docShell);
+
+ bool isReloading = false;
+ uint32_t loadType;
+ docShell->GetLoadType(&loadType);
+ if (loadType == LOAD_RELOAD_NORMAL ||
+ loadType == LOAD_RELOAD_BYPASS_CACHE ||
+ loadType == LOAD_RELOAD_BYPASS_PROXY ||
+ loadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE ||
+ loadType == LOAD_RELOAD_ALLOW_MIXED_CONTENT) {
+ isReloading = true;
+ }
+
+ docAcc->NotifyOfLoading(isReloading);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocManager::OnProgressChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocManager::OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, nsIURI* aLocation,
+ uint32_t aFlags)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocManager::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, nsresult aStatus,
+ const char16_t* aMessage)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocManager::OnSecurityChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aState)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIDOMEventListener
+
+NS_IMETHODIMP
+DocManager::HandleEvent(nsIDOMEvent* aEvent)
+{
+ nsAutoString type;
+ aEvent->GetType(type);
+
+ nsCOMPtr<nsIDocument> document =
+ do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget());
+ NS_ASSERTION(document, "pagehide or DOMContentLoaded for non document!");
+ if (!document)
+ return NS_OK;
+
+ if (type.EqualsLiteral("pagehide")) {
+ // 'pagehide' event is registered on every DOM document we create an
+ // accessible for, process the event for the target. This document
+ // accessible and all its sub document accessible are shutdown as result of
+ // processing.
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocDestroy))
+ logging::DocDestroy("received 'pagehide' event", document);
+#endif
+
+ // Shutdown this one and sub document accessibles.
+
+ // We're allowed to not remove listeners when accessible document is
+ // shutdown since we don't keep strong reference on chrome event target and
+ // listeners are removed automatically when chrome event target goes away.
+ DocAccessible* docAccessible = GetExistingDocAccessible(document);
+ if (docAccessible)
+ docAccessible->Shutdown();
+
+ return NS_OK;
+ }
+
+ // XXX: handle error pages loading separately since they get neither
+ // webprogress notifications nor 'pageshow' event.
+ if (type.EqualsLiteral("DOMContentLoaded") &&
+ nsCoreUtils::IsErrorPage(document)) {
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocLoad))
+ logging::DocLoad("handled 'DOMContentLoaded' event", document);
+#endif
+
+ HandleDOMDocumentLoad(document,
+ nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE);
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DocManager private
+
+void
+DocManager::HandleDOMDocumentLoad(nsIDocument* aDocument,
+ uint32_t aLoadEventType)
+{
+ // Document accessible can be created before we were notified the DOM document
+ // was loaded completely. However if it's not created yet then create it.
+ DocAccessible* docAcc = GetExistingDocAccessible(aDocument);
+ if (!docAcc) {
+ docAcc = CreateDocOrRootAccessible(aDocument);
+ if (!docAcc)
+ return;
+ }
+
+ docAcc->NotifyOfLoad(aLoadEventType);
+}
+
+void
+DocManager::AddListeners(nsIDocument* aDocument,
+ bool aAddDOMContentLoadedListener)
+{
+ nsPIDOMWindowOuter* window = aDocument->GetWindow();
+ EventTarget* target = window->GetChromeEventHandler();
+ EventListenerManager* elm = target->GetOrCreateListenerManager();
+ elm->AddEventListenerByType(this, NS_LITERAL_STRING("pagehide"),
+ TrustedEventsAtCapture());
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocCreate))
+ logging::Text("added 'pagehide' listener");
+#endif
+
+ if (aAddDOMContentLoadedListener) {
+ elm->AddEventListenerByType(this, NS_LITERAL_STRING("DOMContentLoaded"),
+ TrustedEventsAtCapture());
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocCreate))
+ logging::Text("added 'DOMContentLoaded' listener");
+#endif
+ }
+}
+
+void
+DocManager::RemoveListeners(nsIDocument* aDocument)
+{
+ nsPIDOMWindowOuter* window = aDocument->GetWindow();
+ if (!window)
+ return;
+
+ EventTarget* target = window->GetChromeEventHandler();
+ if (!target)
+ return;
+
+ EventListenerManager* elm = target->GetOrCreateListenerManager();
+ elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("pagehide"),
+ TrustedEventsAtCapture());
+
+ elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("DOMContentLoaded"),
+ TrustedEventsAtCapture());
+}
+
+DocAccessible*
+DocManager::CreateDocOrRootAccessible(nsIDocument* aDocument)
+{
+ // Ignore hiding, resource documents and documents without docshell.
+ if (!aDocument->IsVisibleConsideringAncestors() ||
+ aDocument->IsResourceDoc() || !aDocument->IsActive())
+ return nullptr;
+
+ // Ignore documents without presshell and not having root frame.
+ nsIPresShell* presShell = aDocument->GetShell();
+ if (!presShell || presShell->IsDestroying())
+ return nullptr;
+
+ bool isRootDoc = nsCoreUtils::IsRootDocument(aDocument);
+
+ DocAccessible* parentDocAcc = nullptr;
+ if (!isRootDoc) {
+ // XXXaaronl: ideally we would traverse the presshell chain. Since there's
+ // no easy way to do that, we cheat and use the document hierarchy.
+ parentDocAcc = GetDocAccessible(aDocument->GetParentDocument());
+ NS_ASSERTION(parentDocAcc,
+ "Can't create an accessible for the document!");
+ if (!parentDocAcc)
+ return nullptr;
+ }
+
+ // We only create root accessibles for the true root, otherwise create a
+ // doc accessible.
+ RefPtr<DocAccessible> docAcc = isRootDoc ?
+ new RootAccessibleWrap(aDocument, presShell) :
+ new DocAccessibleWrap(aDocument, presShell);
+
+ // Cache the document accessible into document cache.
+ mDocAccessibleCache.Put(aDocument, docAcc);
+
+ // Initialize the document accessible.
+ docAcc->Init();
+
+ // Bind the document to the tree.
+ if (isRootDoc) {
+ if (!ApplicationAcc()->AppendChild(docAcc)) {
+ docAcc->Shutdown();
+ return nullptr;
+ }
+
+ // Fire reorder event to notify new accessible document has been attached to
+ // the tree. The reorder event is delivered after the document tree is
+ // constructed because event processing and tree construction are done by
+ // the same document.
+ // Note: don't use AccReorderEvent to avoid coalsecense and special reorder
+ // events processing.
+ docAcc->FireDelayedEvent(nsIAccessibleEvent::EVENT_REORDER,
+ ApplicationAcc());
+
+ if (IPCAccessibilityActive()) {
+ nsIDocShell* docShell = aDocument->GetDocShell();
+ if (docShell) {
+ nsCOMPtr<nsITabChild> tabChild = docShell->GetTabChild();
+
+ // XXX We may need to handle the case that we don't have a tab child
+ // differently. It may be that this will cause us to fail to notify
+ // the parent process about important accessible documents.
+ if (tabChild) {
+ DocAccessibleChild* ipcDoc = new DocAccessibleChild(docAcc);
+ docAcc->SetIPCDoc(ipcDoc);
+
+#if defined(XP_WIN)
+ IAccessibleHolder holder(CreateHolderFromAccessible(docAcc));
+#endif
+
+ static_cast<TabChild*>(tabChild.get())->
+ SendPDocAccessibleConstructor(ipcDoc, nullptr, 0,
+#if defined(XP_WIN)
+ AccessibleWrap::GetChildIDFor(docAcc),
+ holder
+#else
+ 0, 0
+#endif
+ );
+ }
+ }
+ }
+ } else {
+ parentDocAcc->BindChildDocument(docAcc);
+ }
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocCreate)) {
+ logging::DocCreate("document creation finished", aDocument);
+ logging::Stack();
+ }
+#endif
+
+ AddListeners(aDocument, isRootDoc);
+ return docAcc;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DocManager static
+
+void
+DocManager::ClearDocCache()
+{
+ while (mDocAccessibleCache.Count() > 0) {
+ auto iter = mDocAccessibleCache.Iter();
+ MOZ_ASSERT(!iter.Done());
+ DocAccessible* docAcc = iter.UserData();
+ NS_ASSERTION(docAcc,
+ "No doc accessible for the object in doc accessible cache!");
+ if (docAcc) {
+ docAcc->Shutdown();
+ }
+
+ iter.Remove();
+ }
+
+ // Ensure that all xpcom accessible documents are shut down as well.
+ while (mXPCDocumentCache.Count() > 0) {
+ auto iter = mXPCDocumentCache.Iter();
+ MOZ_ASSERT(!iter.Done());
+ xpcAccessibleDocument* xpcDoc = iter.UserData();
+ NS_ASSERTION(xpcDoc, "No xpc doc for the object in xpc doc cache!");
+
+ if (xpcDoc) {
+ xpcDoc->Shutdown();
+ }
+
+ iter.Remove();
+ }
+}
+
+void
+DocManager::RemoteDocAdded(DocAccessibleParent* aDoc)
+{
+ if (!sRemoteDocuments) {
+ sRemoteDocuments = new nsTArray<DocAccessibleParent*>;
+ ClearOnShutdown(&sRemoteDocuments);
+ }
+
+ MOZ_ASSERT(!sRemoteDocuments->Contains(aDoc),
+ "How did we already have the doc!");
+ sRemoteDocuments->AppendElement(aDoc);
+ ProxyCreated(aDoc, Interfaces::DOCUMENT | Interfaces::HYPERTEXT);
+}
diff --git a/accessible/base/DocManager.h b/accessible/base/DocManager.h
new file mode 100644
index 000000000..b07b6eb01
--- /dev/null
+++ b/accessible/base/DocManager.h
@@ -0,0 +1,189 @@
+/* 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_a11_DocManager_h_
+#define mozilla_a11_DocManager_h_
+
+#include "mozilla/ClearOnShutdown.h"
+#include "nsIDocument.h"
+#include "nsIDOMEventListener.h"
+#include "nsRefPtrHashtable.h"
+#include "nsIWebProgressListener.h"
+#include "nsWeakReference.h"
+#include "nsIPresShell.h"
+#include "mozilla/StaticPtr.h"
+
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+class DocAccessible;
+class xpcAccessibleDocument;
+class DocAccessibleParent;
+
+/**
+ * Manage the document accessible life cycle.
+ */
+class DocManager : public nsIWebProgressListener,
+ public nsIDOMEventListener,
+ public nsSupportsWeakReference
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBPROGRESSLISTENER
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ /**
+ * Return document accessible for the given DOM node.
+ */
+ DocAccessible* GetDocAccessible(nsIDocument* aDocument);
+
+ /**
+ * Return document accessible for the given presshell.
+ */
+ DocAccessible* GetDocAccessible(const nsIPresShell* aPresShell)
+ {
+ if (!aPresShell)
+ return nullptr;
+
+ DocAccessible* doc = aPresShell->GetDocAccessible();
+ if (doc)
+ return doc;
+
+ return GetDocAccessible(aPresShell->GetDocument());
+ }
+
+ /**
+ * Search through all document accessibles for an accessible with the given
+ * unique id.
+ */
+ Accessible* FindAccessibleInCache(nsINode* aNode) const;
+
+ /**
+ * Called by document accessible when it gets shutdown.
+ */
+ void NotifyOfDocumentShutdown(DocAccessible* aDocument,
+ nsIDocument* aDOMDocument);
+
+ /**
+ * Return XPCOM accessible document.
+ */
+ xpcAccessibleDocument* GetXPCDocument(DocAccessible* aDocument);
+ xpcAccessibleDocument* GetCachedXPCDocument(DocAccessible* aDocument) const
+ { return mXPCDocumentCache.GetWeak(aDocument); }
+
+ /*
+ * Notification that a top level document in a content process has gone away.
+ */
+ static void RemoteDocShutdown(DocAccessibleParent* aDoc)
+ {
+ DebugOnly<bool> result = sRemoteDocuments->RemoveElement(aDoc);
+ MOZ_ASSERT(result, "Why didn't we find the document!");
+ }
+
+ /*
+ * Notify of a new top level document in a content process.
+ */
+ static void RemoteDocAdded(DocAccessibleParent* aDoc);
+
+ static const nsTArray<DocAccessibleParent*>* TopLevelRemoteDocs()
+ { return sRemoteDocuments; }
+
+ /**
+ * Remove the xpc document for a remote document if there is one.
+ */
+ static void NotifyOfRemoteDocShutdown(DocAccessibleParent* adoc);
+
+ /**
+ * Get a XPC document for a remote document.
+ */
+ static xpcAccessibleDocument* GetXPCDocument(DocAccessibleParent* aDoc);
+ static xpcAccessibleDocument* GetCachedXPCDocument(const DocAccessibleParent* aDoc)
+ {
+ return sRemoteXPCDocumentCache ? sRemoteXPCDocumentCache->GetWeak(aDoc)
+ : nullptr;
+ }
+
+#ifdef DEBUG
+ bool IsProcessingRefreshDriverNotification() const;
+#endif
+
+protected:
+ DocManager();
+ virtual ~DocManager() { }
+
+ /**
+ * Initialize the manager.
+ */
+ bool Init();
+
+ /**
+ * Shutdown the manager.
+ */
+ void Shutdown();
+
+private:
+ DocManager(const DocManager&);
+ DocManager& operator =(const DocManager&);
+
+private:
+ /**
+ * Create an accessible document if it was't created and fire accessibility
+ * events if needed.
+ *
+ * @param aDocument [in] loaded DOM document
+ * @param aLoadEventType [in] specifies the event type to fire load event,
+ * if 0 then no event is fired
+ */
+ void HandleDOMDocumentLoad(nsIDocument* aDocument,
+ uint32_t aLoadEventType);
+
+ /**
+ * Add/remove 'pagehide' and 'DOMContentLoaded' event listeners.
+ */
+ void AddListeners(nsIDocument *aDocument, bool aAddPageShowListener);
+ void RemoveListeners(nsIDocument* aDocument);
+
+ /**
+ * Create document or root accessible.
+ */
+ DocAccessible* CreateDocOrRootAccessible(nsIDocument* aDocument);
+
+ /**
+ * Clear the cache and shutdown the document accessibles.
+ */
+ void ClearDocCache();
+
+ typedef nsRefPtrHashtable<nsPtrHashKey<const nsIDocument>, DocAccessible>
+ DocAccessibleHashtable;
+ DocAccessibleHashtable mDocAccessibleCache;
+
+ typedef nsRefPtrHashtable<nsPtrHashKey<const DocAccessible>, xpcAccessibleDocument>
+ XPCDocumentHashtable;
+ XPCDocumentHashtable mXPCDocumentCache;
+ static nsRefPtrHashtable<nsPtrHashKey<const DocAccessibleParent>, xpcAccessibleDocument>*
+ sRemoteXPCDocumentCache;
+
+ /*
+ * The list of remote top level documents.
+ */
+ static StaticAutoPtr<nsTArray<DocAccessibleParent*>> sRemoteDocuments;
+};
+
+/**
+ * Return the existing document accessible for the document if any.
+ * Note this returns the doc accessible for the primary pres shell if there is
+ * more than one.
+ */
+inline DocAccessible*
+GetExistingDocAccessible(const nsIDocument* aDocument)
+{
+ nsIPresShell* ps = aDocument->GetShell();
+ return ps ? ps->GetDocAccessible() : nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11_DocManager_h_
diff --git a/accessible/base/EmbeddedObjCollector.cpp b/accessible/base/EmbeddedObjCollector.cpp
new file mode 100644
index 000000000..226d1365f
--- /dev/null
+++ b/accessible/base/EmbeddedObjCollector.cpp
@@ -0,0 +1,81 @@
+/* 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 "EmbeddedObjCollector.h"
+
+#include "Accessible.h"
+
+using namespace mozilla::a11y;
+
+uint32_t
+EmbeddedObjCollector::Count()
+{
+ EnsureNGetIndex(nullptr);
+ return mObjects.Length();
+}
+
+Accessible*
+EmbeddedObjCollector::GetAccessibleAt(uint32_t aIndex)
+{
+ Accessible* accessible = mObjects.SafeElementAt(aIndex, nullptr);
+ if (accessible)
+ return accessible;
+
+ return EnsureNGetObject(aIndex);
+}
+
+Accessible*
+EmbeddedObjCollector::EnsureNGetObject(uint32_t aIndex)
+{
+ uint32_t childCount = mRoot->ChildCount();
+ while (mRootChildIdx < childCount) {
+ Accessible* child = mRoot->GetChildAt(mRootChildIdx++);
+ if (child->IsText())
+ continue;
+
+ AppendObject(child);
+ if (mObjects.Length() - 1 == aIndex)
+ return mObjects[aIndex];
+ }
+
+ return nullptr;
+}
+
+int32_t
+EmbeddedObjCollector::EnsureNGetIndex(Accessible* aAccessible)
+{
+ uint32_t childCount = mRoot->ChildCount();
+ while (mRootChildIdx < childCount) {
+ Accessible* child = mRoot->GetChildAt(mRootChildIdx++);
+ if (child->IsText())
+ continue;
+
+ AppendObject(child);
+ if (child == aAccessible)
+ return mObjects.Length() - 1;
+ }
+
+ return -1;
+}
+
+int32_t
+EmbeddedObjCollector::GetIndexAt(Accessible* aAccessible)
+{
+ if (aAccessible->mParent != mRoot)
+ return -1;
+
+ MOZ_ASSERT(!aAccessible->IsProxy());
+ if (aAccessible->mInt.mIndexOfEmbeddedChild != -1)
+ return aAccessible->mInt.mIndexOfEmbeddedChild;
+
+ return !aAccessible->IsText() ? EnsureNGetIndex(aAccessible) : -1;
+}
+
+void
+EmbeddedObjCollector::AppendObject(Accessible* aAccessible)
+{
+ MOZ_ASSERT(!aAccessible->IsProxy());
+ aAccessible->mInt.mIndexOfEmbeddedChild = mObjects.Length();
+ mObjects.AppendElement(aAccessible);
+}
diff --git a/accessible/base/EmbeddedObjCollector.h b/accessible/base/EmbeddedObjCollector.h
new file mode 100644
index 000000000..b7a189922
--- /dev/null
+++ b/accessible/base/EmbeddedObjCollector.h
@@ -0,0 +1,69 @@
+/* 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_EmbeddedObjCollector_h__
+#define mozilla_a11y_EmbeddedObjCollector_h__
+
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+
+/**
+ * Collect embedded objects. Provide quick access to accessible by index and
+ * vice versa.
+ */
+class EmbeddedObjCollector final
+{
+public:
+ ~EmbeddedObjCollector() { }
+
+ /**
+ * Return index of the given accessible within the collection.
+ */
+ int32_t GetIndexAt(Accessible* aAccessible);
+
+ /**
+ * Return accessible count within the collection.
+ */
+ uint32_t Count();
+
+ /**
+ * Return an accessible from the collection at the given index.
+ */
+ Accessible* GetAccessibleAt(uint32_t aIndex);
+
+protected:
+ /**
+ * Ensure accessible at the given index is stored and return it.
+ */
+ Accessible* EnsureNGetObject(uint32_t aIndex);
+
+ /**
+ * Ensure index for the given accessible is stored and return it.
+ */
+ int32_t EnsureNGetIndex(Accessible* aAccessible);
+
+ // Make sure it's used by Accessible class only.
+ explicit EmbeddedObjCollector(Accessible* aRoot) :
+ mRoot(aRoot), mRootChildIdx(0) {}
+
+ /**
+ * Append the object to collection.
+ */
+ void AppendObject(Accessible* aAccessible);
+
+ friend class Accessible;
+
+ Accessible* mRoot;
+ uint32_t mRootChildIdx;
+ nsTArray<Accessible*> mObjects;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/EventQueue.cpp b/accessible/base/EventQueue.cpp
new file mode 100644
index 000000000..c90f0aef8
--- /dev/null
+++ b/accessible/base/EventQueue.cpp
@@ -0,0 +1,344 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "EventQueue.h"
+
+#include "Accessible-inl.h"
+#include "nsEventShell.h"
+#include "DocAccessible.h"
+#include "DocAccessibleChild.h"
+#include "nsAccessibilityService.h"
+#include "nsTextEquivUtils.h"
+#ifdef A11Y_LOG
+#include "Logging.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+// Defines the number of selection add/remove events in the queue when they
+// aren't packed into single selection within event.
+const unsigned int kSelChangeCountToPack = 5;
+
+////////////////////////////////////////////////////////////////////////////////
+// EventQueue
+////////////////////////////////////////////////////////////////////////////////
+
+bool
+EventQueue::PushEvent(AccEvent* aEvent)
+{
+ NS_ASSERTION((aEvent->mAccessible && aEvent->mAccessible->IsApplication()) ||
+ aEvent->Document() == mDocument,
+ "Queued event belongs to another document!");
+
+ if (!mEvents.AppendElement(aEvent))
+ return false;
+
+ // Filter events.
+ CoalesceEvents();
+
+ if (aEvent->mEventRule != AccEvent::eDoNotEmit &&
+ (aEvent->mEventType == nsIAccessibleEvent::EVENT_NAME_CHANGE ||
+ aEvent->mEventType == nsIAccessibleEvent::EVENT_TEXT_REMOVED ||
+ aEvent->mEventType == nsIAccessibleEvent::EVENT_TEXT_INSERTED)) {
+ PushNameChange(aEvent->mAccessible);
+ }
+ return true;
+}
+
+bool
+EventQueue::PushNameChange(Accessible* aTarget)
+{
+ // Fire name change event on parent given that this event hasn't been
+ // coalesced, the parent's name was calculated from its subtree, and the
+ // subtree was changed.
+ if (aTarget->HasNameDependentParent()) {
+ // Only continue traversing up the tree if it's possible that the parent
+ // accessible's name can depend on this accessible's name.
+ Accessible* parent = aTarget->Parent();
+ while (parent &&
+ nsTextEquivUtils::HasNameRule(parent, eNameFromSubtreeIfReqRule)) {
+ // Test possible name dependent parent.
+ if (nsTextEquivUtils::HasNameRule(parent, eNameFromSubtreeRule)) {
+ nsAutoString name;
+ ENameValueFlag nameFlag = parent->Name(name);
+ // If name is obtained from subtree, fire name change event.
+ if (nameFlag == eNameFromSubtree) {
+ RefPtr<AccEvent> nameChangeEvent =
+ new AccEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, parent);
+ return PushEvent(nameChangeEvent);
+ }
+ break;
+ }
+ parent = parent->Parent();
+ }
+ }
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// EventQueue: private
+
+void
+EventQueue::CoalesceEvents()
+{
+ NS_ASSERTION(mEvents.Length(), "There should be at least one pending event!");
+ uint32_t tail = mEvents.Length() - 1;
+ AccEvent* tailEvent = mEvents[tail];
+
+ switch(tailEvent->mEventRule) {
+ case AccEvent::eCoalesceReorder:
+ {
+ DebugOnly<Accessible*> target = tailEvent->mAccessible.get();
+ MOZ_ASSERT(target->IsApplication() ||
+ target->IsOuterDoc() ||
+ target->IsXULTree(),
+ "Only app or outerdoc accessible reorder events are in the queue");
+ MOZ_ASSERT(tailEvent->GetEventType() == nsIAccessibleEvent::EVENT_REORDER, "only reorder events should be queued");
+ break; // case eCoalesceReorder
+ }
+
+ case AccEvent::eCoalesceOfSameType:
+ {
+ // Coalesce old events by newer event.
+ for (uint32_t index = tail - 1; index < tail; index--) {
+ AccEvent* accEvent = mEvents[index];
+ if (accEvent->mEventType == tailEvent->mEventType &&
+ accEvent->mEventRule == tailEvent->mEventRule) {
+ accEvent->mEventRule = AccEvent::eDoNotEmit;
+ return;
+ }
+ }
+ } break; // case eCoalesceOfSameType
+
+ case AccEvent::eCoalesceSelectionChange:
+ {
+ AccSelChangeEvent* tailSelChangeEvent = downcast_accEvent(tailEvent);
+ for (uint32_t index = tail - 1; index < tail; index--) {
+ AccEvent* thisEvent = mEvents[index];
+ if (thisEvent->mEventRule == tailEvent->mEventRule) {
+ AccSelChangeEvent* thisSelChangeEvent =
+ downcast_accEvent(thisEvent);
+
+ // Coalesce selection change events within same control.
+ if (tailSelChangeEvent->mWidget == thisSelChangeEvent->mWidget) {
+ CoalesceSelChangeEvents(tailSelChangeEvent, thisSelChangeEvent, index);
+ return;
+ }
+ }
+ }
+
+ } break; // eCoalesceSelectionChange
+
+ case AccEvent::eCoalesceStateChange:
+ {
+ // If state change event is duped then ignore previous event. If state
+ // change event is opposite to previous event then no event is emitted
+ // (accessible state wasn't changed).
+ for (uint32_t index = tail - 1; index < tail; index--) {
+ AccEvent* thisEvent = mEvents[index];
+ if (thisEvent->mEventRule != AccEvent::eDoNotEmit &&
+ thisEvent->mEventType == tailEvent->mEventType &&
+ thisEvent->mAccessible == tailEvent->mAccessible) {
+ AccStateChangeEvent* thisSCEvent = downcast_accEvent(thisEvent);
+ AccStateChangeEvent* tailSCEvent = downcast_accEvent(tailEvent);
+ if (thisSCEvent->mState == tailSCEvent->mState) {
+ thisEvent->mEventRule = AccEvent::eDoNotEmit;
+ if (thisSCEvent->mIsEnabled != tailSCEvent->mIsEnabled)
+ tailEvent->mEventRule = AccEvent::eDoNotEmit;
+ }
+ }
+ }
+ break; // eCoalesceStateChange
+ }
+
+ case AccEvent::eCoalesceTextSelChange:
+ {
+ // Coalesce older event by newer event for the same selection or target.
+ // Events for same selection may have different targets and vice versa one
+ // target may be pointed by different selections (for latter see
+ // bug 927159).
+ for (uint32_t index = tail - 1; index < tail; index--) {
+ AccEvent* thisEvent = mEvents[index];
+ if (thisEvent->mEventRule != AccEvent::eDoNotEmit &&
+ thisEvent->mEventType == tailEvent->mEventType) {
+ AccTextSelChangeEvent* thisTSCEvent = downcast_accEvent(thisEvent);
+ AccTextSelChangeEvent* tailTSCEvent = downcast_accEvent(tailEvent);
+ if (thisTSCEvent->mSel == tailTSCEvent->mSel ||
+ thisEvent->mAccessible == tailEvent->mAccessible)
+ thisEvent->mEventRule = AccEvent::eDoNotEmit;
+ }
+
+ }
+ } break; // eCoalesceTextSelChange
+
+ case AccEvent::eRemoveDupes:
+ {
+ // Check for repeat events, coalesce newly appended event by more older
+ // event.
+ for (uint32_t index = tail - 1; index < tail; index--) {
+ AccEvent* accEvent = mEvents[index];
+ if (accEvent->mEventType == tailEvent->mEventType &&
+ accEvent->mEventRule == tailEvent->mEventRule &&
+ accEvent->mAccessible == tailEvent->mAccessible) {
+ tailEvent->mEventRule = AccEvent::eDoNotEmit;
+ return;
+ }
+ }
+ } break; // case eRemoveDupes
+
+ default:
+ break; // case eAllowDupes, eDoNotEmit
+ } // switch
+}
+
+void
+EventQueue::CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
+ AccSelChangeEvent* aThisEvent,
+ uint32_t aThisIndex)
+{
+ aTailEvent->mPreceedingCount = aThisEvent->mPreceedingCount + 1;
+
+ // Pack all preceding events into single selection within event
+ // when we receive too much selection add/remove events.
+ if (aTailEvent->mPreceedingCount >= kSelChangeCountToPack) {
+ aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_WITHIN;
+ aTailEvent->mAccessible = aTailEvent->mWidget;
+ aThisEvent->mEventRule = AccEvent::eDoNotEmit;
+
+ // Do not emit any preceding selection events for same widget if they
+ // weren't coalesced yet.
+ if (aThisEvent->mEventType != nsIAccessibleEvent::EVENT_SELECTION_WITHIN) {
+ for (uint32_t jdx = aThisIndex - 1; jdx < aThisIndex; jdx--) {
+ AccEvent* prevEvent = mEvents[jdx];
+ if (prevEvent->mEventRule == aTailEvent->mEventRule) {
+ AccSelChangeEvent* prevSelChangeEvent =
+ downcast_accEvent(prevEvent);
+ if (prevSelChangeEvent->mWidget == aTailEvent->mWidget)
+ prevSelChangeEvent->mEventRule = AccEvent::eDoNotEmit;
+ }
+ }
+ }
+ return;
+ }
+
+ // Pack sequential selection remove and selection add events into
+ // single selection change event.
+ if (aTailEvent->mPreceedingCount == 1 &&
+ aTailEvent->mItem != aThisEvent->mItem) {
+ if (aTailEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd &&
+ aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionRemove) {
+ aThisEvent->mEventRule = AccEvent::eDoNotEmit;
+ aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION;
+ aTailEvent->mPackedEvent = aThisEvent;
+ return;
+ }
+
+ if (aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd &&
+ aTailEvent->mSelChangeType == AccSelChangeEvent::eSelectionRemove) {
+ aTailEvent->mEventRule = AccEvent::eDoNotEmit;
+ aThisEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION;
+ aThisEvent->mPackedEvent = aTailEvent;
+ return;
+ }
+ }
+
+ // Unpack the packed selection change event because we've got one
+ // more selection add/remove.
+ if (aThisEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION) {
+ if (aThisEvent->mPackedEvent) {
+ aThisEvent->mPackedEvent->mEventType =
+ aThisEvent->mPackedEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd ?
+ nsIAccessibleEvent::EVENT_SELECTION_ADD :
+ nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
+
+ aThisEvent->mPackedEvent->mEventRule =
+ AccEvent::eCoalesceSelectionChange;
+
+ aThisEvent->mPackedEvent = nullptr;
+ }
+
+ aThisEvent->mEventType =
+ aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd ?
+ nsIAccessibleEvent::EVENT_SELECTION_ADD :
+ nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
+
+ return;
+ }
+
+ // Convert into selection add since control has single selection but other
+ // selection events for this control are queued.
+ if (aTailEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION)
+ aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_ADD;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// EventQueue: event queue
+
+void
+EventQueue::ProcessEventQueue()
+{
+ // Process only currently queued events.
+ nsTArray<RefPtr<AccEvent> > events;
+ events.SwapElements(mEvents);
+
+ uint32_t eventCount = events.Length();
+#ifdef A11Y_LOG
+ if (eventCount > 0 && logging::IsEnabled(logging::eEvents)) {
+ logging::MsgBegin("EVENTS", "events processing");
+ logging::Address("document", mDocument);
+ logging::MsgEnd();
+ }
+#endif
+
+ for (uint32_t idx = 0; idx < eventCount; idx++) {
+ AccEvent* event = events[idx];
+ if (event->mEventRule != AccEvent::eDoNotEmit) {
+ Accessible* target = event->GetAccessible();
+ if (!target || target->IsDefunct())
+ continue;
+
+ // Dispatch the focus event if target is still focused.
+ if (event->mEventType == nsIAccessibleEvent::EVENT_FOCUS) {
+ FocusMgr()->ProcessFocusEvent(event);
+ continue;
+ }
+
+ // Dispatch caret moved and text selection change events.
+ if (event->mEventType == nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED) {
+ SelectionMgr()->ProcessTextSelChangeEvent(event);
+ continue;
+ }
+
+ // Fire selected state change events in support to selection events.
+ if (event->mEventType == nsIAccessibleEvent::EVENT_SELECTION_ADD) {
+ nsEventShell::FireEvent(event->mAccessible, states::SELECTED,
+ true, event->mIsFromUserInput);
+
+ } else if (event->mEventType == nsIAccessibleEvent::EVENT_SELECTION_REMOVE) {
+ nsEventShell::FireEvent(event->mAccessible, states::SELECTED,
+ false, event->mIsFromUserInput);
+
+ } else if (event->mEventType == nsIAccessibleEvent::EVENT_SELECTION) {
+ AccSelChangeEvent* selChangeEvent = downcast_accEvent(event);
+ nsEventShell::FireEvent(event->mAccessible, states::SELECTED,
+ (selChangeEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd),
+ event->mIsFromUserInput);
+
+ if (selChangeEvent->mPackedEvent) {
+ nsEventShell::FireEvent(selChangeEvent->mPackedEvent->mAccessible,
+ states::SELECTED,
+ (selChangeEvent->mPackedEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd),
+ selChangeEvent->mPackedEvent->mIsFromUserInput);
+ }
+ }
+
+ nsEventShell::FireEvent(event);
+ }
+
+ if (!mDocument)
+ return;
+ }
+}
diff --git a/accessible/base/EventQueue.h b/accessible/base/EventQueue.h
new file mode 100644
index 000000000..57d1c236d
--- /dev/null
+++ b/accessible/base/EventQueue.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_EventQueue_h_
+#define mozilla_a11y_EventQueue_h_
+
+#include "AccEvent.h"
+
+namespace mozilla {
+namespace a11y {
+
+class DocAccessible;
+
+/**
+ * Used to organize and coalesce pending events.
+ */
+class EventQueue
+{
+protected:
+ explicit EventQueue(DocAccessible* aDocument) : mDocument(aDocument) { }
+
+ /**
+ * Put an accessible event into the queue to process it later.
+ */
+ bool PushEvent(AccEvent* aEvent);
+
+ /**
+ * Puts a name change event into the queue, if needed.
+ */
+ bool PushNameChange(Accessible* aTarget);
+
+ /**
+ * Process events from the queue and fires events.
+ */
+ void ProcessEventQueue();
+
+private:
+ EventQueue(const EventQueue&) = delete;
+ EventQueue& operator = (const EventQueue&) = delete;
+
+ // Event queue processing
+ /**
+ * Coalesce redundant events from the queue.
+ */
+ void CoalesceEvents();
+
+ /**
+ * Coalesce events from the same subtree.
+ */
+ void CoalesceReorderEvents(AccEvent* aTailEvent);
+
+ /**
+ * Coalesce two selection change events within the same select control.
+ */
+ void CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
+ AccSelChangeEvent* aThisEvent,
+ uint32_t aThisIndex);
+
+protected:
+ /**
+ * The document accessible reference owning this queue.
+ */
+ DocAccessible* mDocument;
+
+ /**
+ * Pending events array. Don't make this an AutoTArray; we use
+ * SwapElements() on it.
+ */
+ nsTArray<RefPtr<AccEvent>> mEvents;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_EventQueue_h_
diff --git a/accessible/base/EventTree.cpp b/accessible/base/EventTree.cpp
new file mode 100644
index 000000000..cd4a07ba8
--- /dev/null
+++ b/accessible/base/EventTree.cpp
@@ -0,0 +1,618 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "EventTree.h"
+
+#include "Accessible-inl.h"
+#include "nsEventShell.h"
+#include "DocAccessible.h"
+#ifdef A11Y_LOG
+#include "Logging.h"
+#endif
+
+#include "mozilla/UniquePtr.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// TreeMutation class
+
+EventTree* const TreeMutation::kNoEventTree = reinterpret_cast<EventTree*>(-1);
+
+TreeMutation::TreeMutation(Accessible* aParent, bool aNoEvents) :
+ mParent(aParent), mStartIdx(UINT32_MAX),
+ mStateFlagsCopy(mParent->mStateFlags),
+ mQueueEvents(!aNoEvents)
+{
+#ifdef DEBUG
+ mIsDone = false;
+#endif
+
+#ifdef A11Y_LOG
+ if (mQueueEvents && logging::IsEnabled(logging::eEventTree)) {
+ logging::MsgBegin("EVENTS_TREE", "reordering tree before");
+ logging::AccessibleInfo("reordering for", mParent);
+ Controller()->RootEventTree().Log();
+ logging::MsgEnd();
+
+ if (logging::IsEnabled(logging::eVerbose)) {
+ logging::Tree("EVENTS_TREE", "Container tree", mParent->Document(),
+ PrefixLog, static_cast<void*>(this));
+ }
+ }
+#endif
+
+ mParent->mStateFlags |= Accessible::eKidsMutating;
+}
+
+TreeMutation::~TreeMutation()
+{
+ MOZ_ASSERT(mIsDone, "Done() must be called explicitly");
+}
+
+void
+TreeMutation::AfterInsertion(Accessible* aChild)
+{
+ MOZ_ASSERT(aChild->Parent() == mParent);
+
+ if (static_cast<uint32_t>(aChild->mIndexInParent) < mStartIdx) {
+ mStartIdx = aChild->mIndexInParent + 1;
+ }
+
+ if (!mQueueEvents) {
+ return;
+ }
+
+ RefPtr<AccShowEvent> ev = new AccShowEvent(aChild);
+ DebugOnly<bool> added = Controller()->QueueMutationEvent(ev);
+ MOZ_ASSERT(added);
+ aChild->SetShowEventTarget(true);
+}
+
+void
+TreeMutation::BeforeRemoval(Accessible* aChild, bool aNoShutdown)
+{
+ MOZ_ASSERT(aChild->Parent() == mParent);
+
+ if (static_cast<uint32_t>(aChild->mIndexInParent) < mStartIdx) {
+ mStartIdx = aChild->mIndexInParent;
+ }
+
+ if (!mQueueEvents) {
+ return;
+ }
+
+ RefPtr<AccHideEvent> ev = new AccHideEvent(aChild, !aNoShutdown);
+ if (Controller()->QueueMutationEvent(ev)) {
+ aChild->SetHideEventTarget(true);
+ }
+}
+
+void
+TreeMutation::Done()
+{
+ MOZ_ASSERT(mParent->mStateFlags & Accessible::eKidsMutating);
+ mParent->mStateFlags &= ~Accessible::eKidsMutating;
+
+ uint32_t length = mParent->mChildren.Length();
+#ifdef DEBUG
+ for (uint32_t idx = 0; idx < mStartIdx && idx < length; idx++) {
+ MOZ_ASSERT(mParent->mChildren[idx]->mIndexInParent == static_cast<int32_t>(idx),
+ "Wrong index detected");
+ }
+#endif
+
+ for (uint32_t idx = mStartIdx; idx < length; idx++) {
+ mParent->mChildren[idx]->mInt.mIndexOfEmbeddedChild = -1;
+ mParent->mChildren[idx]->mStateFlags |= Accessible::eGroupInfoDirty;
+ }
+
+ mParent->mEmbeddedObjCollector = nullptr;
+ mParent->mStateFlags |= mStateFlagsCopy & Accessible::eKidsMutating;
+
+#ifdef DEBUG
+ mIsDone = true;
+#endif
+
+#ifdef A11Y_LOG
+ if (mQueueEvents && logging::IsEnabled(logging::eEventTree)) {
+ logging::MsgBegin("EVENTS_TREE", "reordering tree after");
+ logging::AccessibleInfo("reordering for", mParent);
+ Controller()->RootEventTree().Log();
+ logging::MsgEnd();
+ }
+#endif
+}
+
+#ifdef A11Y_LOG
+const char*
+TreeMutation::PrefixLog(void* aData, Accessible* aAcc)
+{
+ TreeMutation* thisObj = reinterpret_cast<TreeMutation*>(aData);
+ if (thisObj->mParent == aAcc) {
+ return "_X_";
+ }
+ const EventTree& ret = thisObj->Controller()->RootEventTree();
+ if (ret.Find(aAcc)) {
+ return "_с_";
+ }
+ return "";
+}
+#endif
+
+
+////////////////////////////////////////////////////////////////////////////////
+// EventTree
+
+void
+EventTree::Shown(Accessible* aChild)
+{
+ RefPtr<AccShowEvent> ev = new AccShowEvent(aChild);
+ Controller(aChild)->WithdrawPrecedingEvents(&ev->mPrecedingEvents);
+ Mutated(ev);
+}
+
+void
+EventTree::Hidden(Accessible* aChild, bool aNeedsShutdown)
+{
+ RefPtr<AccHideEvent> ev = new AccHideEvent(aChild, aNeedsShutdown);
+ if (!aNeedsShutdown) {
+ Controller(aChild)->StorePrecedingEvent(ev);
+ }
+ Mutated(ev);
+}
+
+void
+EventTree::Process(const RefPtr<DocAccessible>& aDeathGrip)
+{
+ while (mFirst) {
+ // Skip a node and its subtree if its container is not in the document.
+ if (mFirst->mContainer->IsInDocument()) {
+ mFirst->Process(aDeathGrip);
+ if (aDeathGrip->IsDefunct()) {
+ return;
+ }
+ }
+ mFirst = Move(mFirst->mNext);
+ }
+
+ MOZ_ASSERT(mContainer || mDependentEvents.IsEmpty(),
+ "No container, no events");
+ MOZ_ASSERT(!mContainer || !mContainer->IsDefunct(),
+ "Processing events for defunct container");
+ MOZ_ASSERT(!mFireReorder || mContainer, "No target for reorder event");
+
+ // Fire mutation events.
+ uint32_t eventsCount = mDependentEvents.Length();
+ for (uint32_t jdx = 0; jdx < eventsCount; jdx++) {
+ AccMutationEvent* mtEvent = mDependentEvents[jdx];
+ MOZ_ASSERT(mtEvent->Document(), "No document for event target");
+
+ // Fire all hide events that has to be fired before this show event.
+ if (mtEvent->IsShow()) {
+ AccShowEvent* showEv = downcast_accEvent(mtEvent);
+ for (uint32_t i = 0; i < showEv->mPrecedingEvents.Length(); i++) {
+ nsEventShell::FireEvent(showEv->mPrecedingEvents[i]);
+ if (aDeathGrip->IsDefunct()) {
+ return;
+ }
+ }
+ }
+
+ nsEventShell::FireEvent(mtEvent);
+ if (aDeathGrip->IsDefunct()) {
+ return;
+ }
+
+ if (mtEvent->mTextChangeEvent) {
+ nsEventShell::FireEvent(mtEvent->mTextChangeEvent);
+ if (aDeathGrip->IsDefunct()) {
+ return;
+ }
+ }
+
+ if (mtEvent->IsHide()) {
+ // Fire menupopup end event before a hide event if a menu goes away.
+
+ // XXX: We don't look into children of hidden subtree to find hiding
+ // menupopup (as we did prior bug 570275) because we don't do that when
+ // menu is showing (and that's impossible until bug 606924 is fixed).
+ // Nevertheless we should do this at least because layout coalesces
+ // the changes before our processing and we may miss some menupopup
+ // events. Now we just want to be consistent in content insertion/removal
+ // handling.
+ if (mtEvent->mAccessible->ARIARole() == roles::MENUPOPUP) {
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END,
+ mtEvent->mAccessible);
+ if (aDeathGrip->IsDefunct()) {
+ return;
+ }
+ }
+
+ AccHideEvent* hideEvent = downcast_accEvent(mtEvent);
+ if (hideEvent->NeedsShutdown()) {
+ aDeathGrip->ShutdownChildrenInSubtree(mtEvent->mAccessible);
+ }
+ }
+ }
+
+ // Fire reorder event at last.
+ if (mFireReorder) {
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_REORDER, mContainer);
+ mContainer->Document()->MaybeNotifyOfValueChange(mContainer);
+ }
+
+ mDependentEvents.Clear();
+}
+
+EventTree*
+EventTree::FindOrInsert(Accessible* aContainer)
+{
+ if (!mFirst) {
+ mFirst.reset(new EventTree(aContainer, mDependentEvents.IsEmpty()));
+ return mFirst.get();
+ }
+
+ EventTree* prevNode = nullptr;
+ EventTree* node = mFirst.get();
+ do {
+ MOZ_ASSERT(!node->mContainer->IsApplication(),
+ "No event for application accessible is expected here");
+ MOZ_ASSERT(!node->mContainer->IsDefunct(), "An event target has to be alive");
+
+ // Case of same target.
+ if (node->mContainer == aContainer) {
+ return node;
+ }
+
+ // Check if the given container is contained by a current node
+ Accessible* top = mContainer ? mContainer : aContainer->Document();
+ Accessible* parent = aContainer;
+ while (parent) {
+ // Reached a top, no match for a current event.
+ if (parent == top) {
+ break;
+ }
+
+ // We got a match.
+ if (parent->Parent() == node->mContainer) {
+ // Reject the node if it's contained by a show/hide event target
+ uint32_t evCount = node->mDependentEvents.Length();
+ for (uint32_t idx = 0; idx < evCount; idx++) {
+ AccMutationEvent* ev = node->mDependentEvents[idx];
+ if (ev->GetAccessible() == parent) {
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eEventTree)) {
+ logging::MsgBegin("EVENTS_TREE",
+ "Rejecting node contained by show/hide");
+ logging::AccessibleInfo("Node", aContainer);
+ logging::MsgEnd();
+ }
+#endif
+ // If the node is rejected, then check if it has related hide event
+ // on stack, and if so, then connect it to the parent show event.
+ if (ev->IsShow()) {
+ AccShowEvent* showEv = downcast_accEvent(ev);
+ Controller(aContainer)->
+ WithdrawPrecedingEvents(&showEv->mPrecedingEvents);
+ }
+ return nullptr;
+ }
+ }
+
+ return node->FindOrInsert(aContainer);
+ }
+
+ parent = parent->Parent();
+ MOZ_ASSERT(parent, "Wrong tree");
+ }
+
+ // If the given container contains a current node
+ // then
+ // if show or hide of the given node contains a grand parent of the current node
+ // then ignore the current node and its show and hide events
+ // otherwise ignore the current node, but not its show and hide events
+ Accessible* curParent = node->mContainer;
+ while (curParent && !curParent->IsDoc()) {
+ if (curParent->Parent() != aContainer) {
+ curParent = curParent->Parent();
+ continue;
+ }
+
+ // Insert the tail node into the hierarchy between the current node and
+ // its parent.
+ node->mFireReorder = false;
+ UniquePtr<EventTree>& nodeOwnerRef = prevNode ? prevNode->mNext : mFirst;
+ UniquePtr<EventTree> newNode(new EventTree(aContainer, mDependentEvents.IsEmpty()));
+ newNode->mFirst = Move(nodeOwnerRef);
+ nodeOwnerRef = Move(newNode);
+ nodeOwnerRef->mNext = Move(node->mNext);
+
+ // Check if a next node is contained by the given node too, and move them
+ // under the given node if so.
+ prevNode = nodeOwnerRef.get();
+ node = nodeOwnerRef->mNext.get();
+ UniquePtr<EventTree>* nodeRef = &nodeOwnerRef->mNext;
+ EventTree* insNode = nodeOwnerRef->mFirst.get();
+ while (node) {
+ Accessible* curParent = node->mContainer;
+ while (curParent && !curParent->IsDoc()) {
+ if (curParent->Parent() != aContainer) {
+ curParent = curParent->Parent();
+ continue;
+ }
+
+ MOZ_ASSERT(!insNode->mNext);
+
+ node->mFireReorder = false;
+ insNode->mNext = Move(*nodeRef);
+ insNode = insNode->mNext.get();
+
+ prevNode->mNext = Move(node->mNext);
+ node = prevNode;
+ break;
+ }
+
+ prevNode = node;
+ nodeRef = &node->mNext;
+ node = node->mNext.get();
+ }
+
+ return nodeOwnerRef.get();
+ }
+
+ prevNode = node;
+ } while ((node = node->mNext.get()));
+
+ MOZ_ASSERT(prevNode, "Nowhere to insert");
+ MOZ_ASSERT(!prevNode->mNext, "Taken by another node");
+
+ // If 'this' node contains the given container accessible, then
+ // do not emit a reorder event for the container
+ // if a dependent show event target contains the given container then do not
+ // emit show / hide events (see Process() method)
+
+ prevNode->mNext.reset(new EventTree(aContainer, mDependentEvents.IsEmpty()));
+ return prevNode->mNext.get();
+}
+
+void
+EventTree::Clear()
+{
+ mFirst = nullptr;
+ mNext = nullptr;
+ mContainer = nullptr;
+
+ uint32_t eventsCount = mDependentEvents.Length();
+ for (uint32_t jdx = 0; jdx < eventsCount; jdx++) {
+ mDependentEvents[jdx]->mEventType = AccEvent::eDoNotEmit;
+ AccHideEvent* ev = downcast_accEvent(mDependentEvents[jdx]);
+ if (ev && ev->NeedsShutdown()) {
+ ev->Document()->ShutdownChildrenInSubtree(ev->mAccessible);
+ }
+ }
+ mDependentEvents.Clear();
+}
+
+const EventTree*
+EventTree::Find(const Accessible* aContainer) const
+{
+ const EventTree* et = this;
+ while (et) {
+ if (et->mContainer == aContainer) {
+ return et;
+ }
+
+ if (et->mFirst) {
+ et = et->mFirst.get();
+ const EventTree* cet = et->Find(aContainer);
+ if (cet) {
+ return cet;
+ }
+ }
+
+ et = et->mNext.get();
+ const EventTree* cet = et->Find(aContainer);
+ if (cet) {
+ return cet;
+ }
+ }
+
+ return nullptr;
+}
+
+#ifdef A11Y_LOG
+void
+EventTree::Log(uint32_t aLevel) const
+{
+ if (aLevel == UINT32_MAX) {
+ if (mFirst) {
+ mFirst->Log(0);
+ }
+ return;
+ }
+
+ for (uint32_t i = 0; i < aLevel; i++) {
+ printf(" ");
+ }
+ logging::AccessibleInfo("container", mContainer);
+
+ for (uint32_t i = 0; i < mDependentEvents.Length(); i++) {
+ AccMutationEvent* ev = mDependentEvents[i];
+ if (ev->IsShow()) {
+ for (uint32_t i = 0; i < aLevel + 1; i++) {
+ printf(" ");
+ }
+ logging::AccessibleInfo("shown", ev->mAccessible);
+
+ AccShowEvent* showEv = downcast_accEvent(ev);
+ for (uint32_t i = 0; i < showEv->mPrecedingEvents.Length(); i++) {
+ for (uint32_t j = 0; j < aLevel + 1; j++) {
+ printf(" ");
+ }
+ logging::AccessibleInfo("preceding",
+ showEv->mPrecedingEvents[i]->mAccessible);
+ }
+ }
+ else {
+ for (uint32_t i = 0; i < aLevel + 1; i++) {
+ printf(" ");
+ }
+ logging::AccessibleInfo("hidden", ev->mAccessible);
+ }
+ }
+
+ if (mFirst) {
+ mFirst->Log(aLevel + 1);
+ }
+
+ if (mNext) {
+ mNext->Log(aLevel);
+ }
+}
+#endif
+
+void
+EventTree::Mutated(AccMutationEvent* aEv)
+{
+ // If shown or hidden node is a root of previously mutated subtree, then
+ // discard those subtree mutations as we are no longer interested in them.
+ UniquePtr<EventTree>* node = &mFirst;
+ while (*node) {
+ Accessible* cntr = (*node)->mContainer;
+ while (cntr != mContainer) {
+ if (cntr == aEv->mAccessible) {
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eEventTree)) {
+ logging::MsgBegin("EVENTS_TREE", "Trim subtree");
+ logging::AccessibleInfo("Show/hide container", aEv->mAccessible);
+ logging::AccessibleInfo("Trimmed subtree root", (*node)->mContainer);
+ logging::MsgEnd();
+ }
+#endif
+
+ // If the new hide is part of a move and it contains existing child
+ // shows, then move preceding events from the child shows to the buffer,
+ // so the ongoing show event will pick them up.
+ if (aEv->IsHide()) {
+ AccHideEvent* hideEv = downcast_accEvent(aEv);
+ if (!hideEv->mNeedsShutdown) {
+ for (uint32_t i = 0; i < (*node)->mDependentEvents.Length(); i++) {
+ AccMutationEvent* childEv = (*node)->mDependentEvents[i];
+ if (childEv->IsShow()) {
+ AccShowEvent* childShowEv = downcast_accEvent(childEv);
+ if (childShowEv->mPrecedingEvents.Length() > 0) {
+ Controller(mContainer)->StorePrecedingEvents(
+ mozilla::Move(childShowEv->mPrecedingEvents));
+ }
+ }
+ }
+ }
+ }
+ // If the new show contains existing child shows, then move preceding
+ // events from the child shows to the new show.
+ else if (aEv->IsShow()) {
+ AccShowEvent* showEv = downcast_accEvent(aEv);
+ for (uint32_t i = 0; (*node)->mDependentEvents.Length(); i++) {
+ AccMutationEvent* childEv = (*node)->mDependentEvents[i];
+ if (childEv->IsShow()) {
+ AccShowEvent* showChildEv = downcast_accEvent(childEv);
+ if (showChildEv->mPrecedingEvents.Length() > 0) {
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eEventTree)) {
+ logging::MsgBegin("EVENTS_TREE", "Adopt preceding events");
+ logging::AccessibleInfo("Parent", aEv->mAccessible);
+ for (uint32_t j = 0; j < showChildEv->mPrecedingEvents.Length(); j++) {
+ logging::AccessibleInfo("Adoptee",
+ showChildEv->mPrecedingEvents[i]->mAccessible);
+ }
+ logging::MsgEnd();
+ }
+#endif
+ showEv->mPrecedingEvents.AppendElements(showChildEv->mPrecedingEvents);
+ }
+ }
+ }
+ }
+
+ *node = Move((*node)->mNext);
+ break;
+ }
+ cntr = cntr->Parent();
+ }
+ if (cntr == aEv->mAccessible) {
+ continue;
+ }
+ node = &(*node)->mNext;
+ }
+
+ AccMutationEvent* prevEvent = mDependentEvents.SafeLastElement(nullptr);
+ mDependentEvents.AppendElement(aEv);
+
+ // Coalesce text change events from this hide/show event and the previous one.
+ if (prevEvent && aEv->mEventType == prevEvent->mEventType) {
+ if (aEv->IsHide()) {
+ // XXX: we need a way to ignore SplitNode and JoinNode() when they do not
+ // affect the text within the hypertext.
+ AccTextChangeEvent* prevTextEvent = prevEvent->mTextChangeEvent;
+ if (prevTextEvent) {
+ AccHideEvent* hideEvent = downcast_accEvent(aEv);
+ AccHideEvent* prevHideEvent = downcast_accEvent(prevEvent);
+
+ if (prevHideEvent->mNextSibling == hideEvent->mAccessible) {
+ hideEvent->mAccessible->AppendTextTo(prevTextEvent->mModifiedText);
+ }
+ else if (prevHideEvent->mPrevSibling == hideEvent->mAccessible) {
+ uint32_t oldLen = prevTextEvent->GetLength();
+ hideEvent->mAccessible->AppendTextTo(prevTextEvent->mModifiedText);
+ prevTextEvent->mStart -= prevTextEvent->GetLength() - oldLen;
+ }
+
+ hideEvent->mTextChangeEvent.swap(prevEvent->mTextChangeEvent);
+ }
+ }
+ else {
+ AccTextChangeEvent* prevTextEvent = prevEvent->mTextChangeEvent;
+ if (prevTextEvent) {
+ if (aEv->mAccessible->IndexInParent() ==
+ prevEvent->mAccessible->IndexInParent() + 1) {
+ // If tail target was inserted after this target, i.e. tail target is next
+ // sibling of this target.
+ aEv->mAccessible->AppendTextTo(prevTextEvent->mModifiedText);
+ }
+ else if (aEv->mAccessible->IndexInParent() ==
+ prevEvent->mAccessible->IndexInParent() - 1) {
+ // If tail target was inserted before this target, i.e. tail target is
+ // previous sibling of this target.
+ nsAutoString startText;
+ aEv->mAccessible->AppendTextTo(startText);
+ prevTextEvent->mModifiedText = startText + prevTextEvent->mModifiedText;
+ prevTextEvent->mStart -= startText.Length();
+ }
+
+ aEv->mTextChangeEvent.swap(prevEvent->mTextChangeEvent);
+ }
+ }
+ }
+
+ // Create a text change event caused by this hide/show event. When a node is
+ // hidden/removed or shown/appended, the text in an ancestor hyper text will
+ // lose or get new characters.
+ if (aEv->mTextChangeEvent || !mContainer->IsHyperText()) {
+ return;
+ }
+
+ nsAutoString text;
+ aEv->mAccessible->AppendTextTo(text);
+ if (text.IsEmpty()) {
+ return;
+ }
+
+ int32_t offset = mContainer->AsHyperText()->GetChildOffset(aEv->mAccessible);
+ aEv->mTextChangeEvent =
+ new AccTextChangeEvent(mContainer, offset, text, aEv->IsShow(),
+ aEv->mIsFromUserInput ? eFromUserInput : eNoUserInput);
+}
diff --git a/accessible/base/EventTree.h b/accessible/base/EventTree.h
new file mode 100644
index 000000000..932ceaf4e
--- /dev/null
+++ b/accessible/base/EventTree.h
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_EventTree_h_
+#define mozilla_a11y_EventTree_h_
+
+#include "AccEvent.h"
+#include "Accessible.h"
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * This class makes sure required tasks are done before and after tree
+ * mutations. Currently this only includes group info invalidation. You must
+ * have an object of this class on the stack when calling methods that mutate
+ * the accessible tree.
+ */
+class TreeMutation final
+{
+public:
+ static const bool kNoEvents = true;
+ static const bool kNoShutdown = true;
+
+ explicit TreeMutation(Accessible* aParent, bool aNoEvents = false);
+ ~TreeMutation();
+
+ void AfterInsertion(Accessible* aChild);
+ void BeforeRemoval(Accessible* aChild, bool aNoShutdown = false);
+ void Done();
+
+private:
+ NotificationController* Controller() const
+ { return mParent->Document()->Controller(); }
+
+ static EventTree* const kNoEventTree;
+
+#ifdef A11Y_LOG
+ static const char* PrefixLog(void* aData, Accessible*);
+#endif
+
+ Accessible* mParent;
+ uint32_t mStartIdx;
+ uint32_t mStateFlagsCopy;
+
+ /*
+ * True if mutation events should be queued.
+ */
+ bool mQueueEvents;
+
+#ifdef DEBUG
+ bool mIsDone;
+#endif
+};
+
+
+/**
+ * A mutation events coalescence structure.
+ */
+class EventTree final {
+public:
+ EventTree() :
+ mFirst(nullptr), mNext(nullptr), mContainer(nullptr), mFireReorder(false) { }
+ explicit EventTree(Accessible* aContainer, bool aFireReorder) :
+ mFirst(nullptr), mNext(nullptr), mContainer(aContainer),
+ mFireReorder(aFireReorder) { }
+ ~EventTree() { Clear(); }
+
+ void Shown(Accessible* aTarget);
+ void Hidden(Accessible*, bool);
+
+ /**
+ * Return an event tree node for the given accessible.
+ */
+ const EventTree* Find(const Accessible* aContainer) const;
+
+ /**
+ * Add a mutation event to this event tree.
+ */
+ void Mutated(AccMutationEvent* aEv);
+
+#ifdef A11Y_LOG
+ void Log(uint32_t aLevel = UINT32_MAX) const;
+#endif
+
+private:
+ /**
+ * Processes the event queue and fires events.
+ */
+ void Process(const RefPtr<DocAccessible>& aDeathGrip);
+
+ /**
+ * Return an event subtree for the given accessible.
+ */
+ EventTree* FindOrInsert(Accessible* aContainer);
+
+ void Clear();
+
+ UniquePtr<EventTree> mFirst;
+ UniquePtr<EventTree> mNext;
+
+ Accessible* mContainer;
+ nsTArray<RefPtr<AccMutationEvent>> mDependentEvents;
+ bool mFireReorder;
+
+ static NotificationController* Controller(Accessible* aAcc)
+ { return aAcc->Document()->Controller(); }
+
+ friend class NotificationController;
+};
+
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_EventQueue_h_
diff --git a/accessible/base/Filters.cpp b/accessible/base/Filters.cpp
new file mode 100644
index 000000000..ea74e3602
--- /dev/null
+++ b/accessible/base/Filters.cpp
@@ -0,0 +1,51 @@
+/* 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 "Filters.h"
+
+#include "Accessible-inl.h"
+#include "nsAccUtils.h"
+#include "Role.h"
+#include "States.h"
+
+using namespace mozilla::a11y;
+using namespace mozilla::a11y::filters;
+
+uint32_t
+filters::GetSelected(Accessible* aAccessible)
+{
+ if (aAccessible->State() & states::SELECTED)
+ return eMatch | eSkipSubtree;
+
+ return eSkip;
+}
+
+uint32_t
+filters::GetSelectable(Accessible* aAccessible)
+{
+ if (aAccessible->InteractiveState() & states::SELECTABLE)
+ return eMatch | eSkipSubtree;
+
+ return eSkip;
+}
+
+uint32_t
+filters::GetRow(Accessible* aAccessible)
+{
+ a11y::role role = aAccessible->Role();
+ if (role == roles::ROW)
+ return eMatch | eSkipSubtree;
+
+ // Look for rows inside rowgroup.
+ if (role == roles::GROUPING)
+ return eSkip;
+
+ return eSkipSubtree;
+}
+
+uint32_t
+filters::GetCell(Accessible* aAccessible)
+{
+ return aAccessible->IsTableCell() ? eMatch : eSkipSubtree;
+}
diff --git a/accessible/base/Filters.h b/accessible/base/Filters.h
new file mode 100644
index 000000000..8ff2bc558
--- /dev/null
+++ b/accessible/base/Filters.h
@@ -0,0 +1,50 @@
+/* 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_Filters_h__
+#define mozilla_a11y_Filters_h__
+
+#include <stdint.h>
+
+/**
+ * Predefined filters used for nsAccIterator and nsAccCollector.
+ */
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+
+namespace filters {
+
+enum EResult {
+ eSkip = 0,
+ eMatch = 1,
+ eSkipSubtree = 2
+};
+
+/**
+ * Return true if the traversed accessible complies with filter.
+ */
+typedef uint32_t (*FilterFuncPtr) (Accessible*);
+
+/**
+ * Matches selected/selectable accessibles in subtree.
+ */
+uint32_t GetSelected(Accessible* aAccessible);
+uint32_t GetSelectable(Accessible* aAccessible);
+
+/**
+ * Matches row accessibles in subtree.
+ */
+uint32_t GetRow(Accessible* aAccessible);
+
+/**
+ * Matches cell accessibles in children.
+ */
+uint32_t GetCell(Accessible* aAccessible);
+} // namespace filters
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/FocusManager.cpp b/accessible/base/FocusManager.cpp
new file mode 100644
index 000000000..e3575415d
--- /dev/null
+++ b/accessible/base/FocusManager.cpp
@@ -0,0 +1,404 @@
+/* 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 "FocusManager.h"
+
+#include "Accessible-inl.h"
+#include "AccIterator.h"
+#include "DocAccessible-inl.h"
+#include "nsAccessibilityService.h"
+#include "nsAccUtils.h"
+#include "nsEventShell.h"
+#include "Role.h"
+
+#include "nsFocusManager.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/dom/Element.h"
+
+namespace mozilla {
+namespace a11y {
+
+FocusManager::FocusManager()
+{
+}
+
+FocusManager::~FocusManager()
+{
+}
+
+Accessible*
+FocusManager::FocusedAccessible() const
+{
+ if (mActiveItem)
+ return mActiveItem;
+
+ nsINode* focusedNode = FocusedDOMNode();
+ if (focusedNode) {
+ DocAccessible* doc =
+ GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
+ return doc ? doc->GetAccessibleEvenIfNotInMapOrContainer(focusedNode) : nullptr;
+ }
+
+ return nullptr;
+}
+
+bool
+FocusManager::IsFocused(const Accessible* aAccessible) const
+{
+ if (mActiveItem)
+ return mActiveItem == aAccessible;
+
+ nsINode* focusedNode = FocusedDOMNode();
+ if (focusedNode) {
+ // XXX: Before getting an accessible for node having a DOM focus make sure
+ // they belong to the same document because it can trigger unwanted document
+ // accessible creation for temporary about:blank document. Without this
+ // peculiarity we would end up with plain implementation based on
+ // FocusedAccessible() method call. Make sure this issue is fixed in
+ // bug 638465.
+ if (focusedNode->OwnerDoc() == aAccessible->GetNode()->OwnerDoc()) {
+ DocAccessible* doc =
+ GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
+ return aAccessible ==
+ (doc ? doc->GetAccessibleEvenIfNotInMapOrContainer(focusedNode) : nullptr);
+ }
+ }
+ return false;
+}
+
+bool
+FocusManager::IsFocusWithin(const Accessible* aContainer) const
+{
+ Accessible* child = FocusedAccessible();
+ while (child) {
+ if (child == aContainer)
+ return true;
+
+ child = child->Parent();
+ }
+ return false;
+}
+
+FocusManager::FocusDisposition
+FocusManager::IsInOrContainsFocus(const Accessible* aAccessible) const
+{
+ Accessible* focus = FocusedAccessible();
+ if (!focus)
+ return eNone;
+
+ // If focused.
+ if (focus == aAccessible)
+ return eFocused;
+
+ // If contains the focus.
+ Accessible* child = focus->Parent();
+ while (child) {
+ if (child == aAccessible)
+ return eContainsFocus;
+
+ child = child->Parent();
+ }
+
+ // If contained by focus.
+ child = aAccessible->Parent();
+ while (child) {
+ if (child == focus)
+ return eContainedByFocus;
+
+ child = child->Parent();
+ }
+
+ return eNone;
+}
+
+void
+FocusManager::NotifyOfDOMFocus(nsISupports* aTarget)
+{
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::FocusNotificationTarget("DOM focus", "Target", aTarget);
+#endif
+
+ mActiveItem = nullptr;
+
+ nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget));
+ if (targetNode) {
+ DocAccessible* document =
+ GetAccService()->GetDocAccessible(targetNode->OwnerDoc());
+ if (document) {
+ // Set selection listener for focused element.
+ if (targetNode->IsElement())
+ SelectionMgr()->SetControlSelectionListener(targetNode->AsElement());
+
+ document->HandleNotification<FocusManager, nsINode>
+ (this, &FocusManager::ProcessDOMFocus, targetNode);
+ }
+ }
+}
+
+void
+FocusManager::NotifyOfDOMBlur(nsISupports* aTarget)
+{
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::FocusNotificationTarget("DOM blur", "Target", aTarget);
+#endif
+
+ mActiveItem = nullptr;
+
+ // If DOM document stays focused then fire accessible focus event to process
+ // the case when no element within this DOM document will be focused.
+ nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget));
+ if (targetNode && targetNode->OwnerDoc() == FocusedDOMDocument()) {
+ nsIDocument* DOMDoc = targetNode->OwnerDoc();
+ DocAccessible* document =
+ GetAccService()->GetDocAccessible(DOMDoc);
+ if (document) {
+ // Clear selection listener for previously focused element.
+ if (targetNode->IsElement())
+ SelectionMgr()->ClearControlSelectionListener();
+
+ document->HandleNotification<FocusManager, nsINode>
+ (this, &FocusManager::ProcessDOMFocus, DOMDoc);
+ }
+ }
+}
+
+void
+FocusManager::ActiveItemChanged(Accessible* aItem, bool aCheckIfActive)
+{
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::FocusNotificationTarget("active item changed", "Item", aItem);
+#endif
+
+ // Nothing changed, happens for XUL trees and HTML selects.
+ if (aItem && aItem == mActiveItem)
+ return;
+
+ mActiveItem = nullptr;
+
+ if (aItem && aCheckIfActive) {
+ Accessible* widget = aItem->ContainerWidget();
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::ActiveWidget(widget);
+#endif
+ if (!widget || !widget->IsActiveWidget() || !widget->AreItemsOperable())
+ return;
+ }
+ mActiveItem = aItem;
+
+ // If active item is changed then fire accessible focus event on it, otherwise
+ // if there's no an active item then fire focus event to accessible having
+ // DOM focus.
+ Accessible* target = FocusedAccessible();
+ if (target)
+ DispatchFocusEvent(target->Document(), target);
+}
+
+void
+FocusManager::ForceFocusEvent()
+{
+ nsINode* focusedNode = FocusedDOMNode();
+ if (focusedNode) {
+ DocAccessible* document =
+ GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
+ if (document) {
+ document->HandleNotification<FocusManager, nsINode>
+ (this, &FocusManager::ProcessDOMFocus, focusedNode);
+ }
+ }
+}
+
+void
+FocusManager::DispatchFocusEvent(DocAccessible* aDocument,
+ Accessible* aTarget)
+{
+ NS_PRECONDITION(aDocument, "No document for focused accessible!");
+ if (aDocument) {
+ RefPtr<AccEvent> event =
+ new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, aTarget,
+ eAutoDetect, AccEvent::eCoalesceOfSameType);
+ aDocument->FireDelayedEvent(event);
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::FocusDispatched(aTarget);
+#endif
+ }
+}
+
+void
+FocusManager::ProcessDOMFocus(nsINode* aTarget)
+{
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::FocusNotificationTarget("process DOM focus", "Target", aTarget);
+#endif
+
+ DocAccessible* document =
+ GetAccService()->GetDocAccessible(aTarget->OwnerDoc());
+ if (!document)
+ return;
+
+ Accessible* target = document->GetAccessibleEvenIfNotInMapOrContainer(aTarget);
+ if (target) {
+ // Check if still focused. Otherwise we can end up with storing the active
+ // item for control that isn't focused anymore.
+ nsINode* focusedNode = FocusedDOMNode();
+ if (!focusedNode)
+ return;
+
+ Accessible* DOMFocus =
+ document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode);
+ if (target != DOMFocus)
+ return;
+
+ Accessible* activeItem = target->CurrentItem();
+ if (activeItem) {
+ mActiveItem = activeItem;
+ target = activeItem;
+ }
+
+ DispatchFocusEvent(document, target);
+ }
+}
+
+void
+FocusManager::ProcessFocusEvent(AccEvent* aEvent)
+{
+ NS_PRECONDITION(aEvent->GetEventType() == nsIAccessibleEvent::EVENT_FOCUS,
+ "Focus event is expected!");
+
+ // Emit focus event if event target is the active item. Otherwise then check
+ // if it's still focused and then update active item and emit focus event.
+ Accessible* target = aEvent->GetAccessible();
+ if (target != mActiveItem) {
+
+ // Check if still focused. Otherwise we can end up with storing the active
+ // item for control that isn't focused anymore.
+ DocAccessible* document = aEvent->Document();
+ nsINode* focusedNode = FocusedDOMNode();
+ if (!focusedNode)
+ return;
+
+ Accessible* DOMFocus =
+ document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode);
+ if (target != DOMFocus)
+ return;
+
+ Accessible* activeItem = target->CurrentItem();
+ if (activeItem) {
+ mActiveItem = activeItem;
+ target = activeItem;
+ }
+ }
+
+ // Fire menu start/end events for ARIA menus.
+ if (target->IsARIARole(nsGkAtoms::menuitem)) {
+ // The focus was moved into menu.
+ Accessible* ARIAMenubar = nullptr;
+ for (Accessible* parent = target->Parent(); parent; parent = parent->Parent()) {
+ if (parent->IsARIARole(nsGkAtoms::menubar)) {
+ ARIAMenubar = parent;
+ break;
+ }
+
+ // Go up in the parent chain of the menu hierarchy.
+ if (!parent->IsARIARole(nsGkAtoms::menuitem) &&
+ !parent->IsARIARole(nsGkAtoms::menu)) {
+ break;
+ }
+ }
+
+ if (ARIAMenubar != mActiveARIAMenubar) {
+ // Leaving ARIA menu. Fire menu_end event on current menubar.
+ if (mActiveARIAMenubar) {
+ RefPtr<AccEvent> menuEndEvent =
+ new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar,
+ aEvent->FromUserInput());
+ nsEventShell::FireEvent(menuEndEvent);
+ }
+
+ mActiveARIAMenubar = ARIAMenubar;
+
+ // Entering ARIA menu. Fire menu_start event.
+ if (mActiveARIAMenubar) {
+ RefPtr<AccEvent> menuStartEvent =
+ new AccEvent(nsIAccessibleEvent::EVENT_MENU_START,
+ mActiveARIAMenubar, aEvent->FromUserInput());
+ nsEventShell::FireEvent(menuStartEvent);
+ }
+ }
+ } else if (mActiveARIAMenubar) {
+ // Focus left a menu. Fire menu_end event.
+ RefPtr<AccEvent> menuEndEvent =
+ new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar,
+ aEvent->FromUserInput());
+ nsEventShell::FireEvent(menuEndEvent);
+
+ mActiveARIAMenubar = nullptr;
+ }
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::FocusNotificationTarget("fire focus event", "Target", target);
+#endif
+
+ // Reset cached caret value. The cache will be updated upon processing the
+ // next caret move event. This ensures that we will return the correct caret
+ // offset before the caret move event is handled.
+ SelectionMgr()->ResetCaretOffset();
+
+ RefPtr<AccEvent> focusEvent =
+ new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, target, aEvent->FromUserInput());
+ nsEventShell::FireEvent(focusEvent);
+
+ // Fire scrolling_start event when the document receives the focus if it has
+ // an anchor jump. If an accessible within the document receive the focus
+ // then null out the anchor jump because it no longer applies.
+ DocAccessible* targetDocument = target->Document();
+ Accessible* anchorJump = targetDocument->AnchorJump();
+ if (anchorJump) {
+ if (target == targetDocument) {
+ // XXX: bug 625699, note in some cases the node could go away before we
+ // we receive focus event, for example if the node is removed from DOM.
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_START,
+ anchorJump, aEvent->FromUserInput());
+ }
+ targetDocument->SetAnchorJump(nullptr);
+ }
+}
+
+nsINode*
+FocusManager::FocusedDOMNode() const
+{
+ nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager();
+ nsIContent* focusedElm = DOMFocusManager->GetFocusedContent();
+
+ // No focus on remote target elements like xul:browser having DOM focus and
+ // residing in chrome process because it means an element in content process
+ // keeps the focus.
+ if (focusedElm) {
+ if (EventStateManager::IsRemoteTarget(focusedElm)) {
+ return nullptr;
+ }
+ return focusedElm;
+ }
+
+ // Otherwise the focus can be on DOM document.
+ nsPIDOMWindowOuter* focusedWnd = DOMFocusManager->GetFocusedWindow();
+ return focusedWnd ? focusedWnd->GetExtantDoc() : nullptr;
+}
+
+nsIDocument*
+FocusManager::FocusedDOMDocument() const
+{
+ nsINode* focusedNode = FocusedDOMNode();
+ return focusedNode ? focusedNode->OwnerDoc() : nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
diff --git a/accessible/base/FocusManager.h b/accessible/base/FocusManager.h
new file mode 100644
index 000000000..633f9ccb2
--- /dev/null
+++ b/accessible/base/FocusManager.h
@@ -0,0 +1,132 @@
+/* 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_FocusManager_h_
+#define mozilla_a11y_FocusManager_h_
+
+class nsINode;
+class nsIDocument;
+class nsISupports;
+
+namespace mozilla {
+namespace a11y {
+
+class AccEvent;
+class Accessible;
+class DocAccessible;
+
+/**
+ * Manage the accessible focus. Used to fire and process accessible events.
+ */
+class FocusManager
+{
+public:
+ virtual ~FocusManager();
+
+ /**
+ * Return a focused accessible.
+ */
+ Accessible* FocusedAccessible() const;
+
+ /**
+ * Return true if given accessible is focused.
+ */
+ bool IsFocused(const Accessible* aAccessible) const;
+
+ /**
+ * Return true if the given accessible is an active item, i.e. an item that
+ * is current within the active widget.
+ */
+ inline bool IsActiveItem(const Accessible* aAccessible)
+ { return aAccessible == mActiveItem; }
+
+ /**
+ * Return true if given DOM node has DOM focus.
+ */
+ inline bool HasDOMFocus(const nsINode* aNode) const
+ { return aNode == FocusedDOMNode(); }
+
+ /**
+ * Return true if focused accessible is within the given container.
+ */
+ bool IsFocusWithin(const Accessible* aContainer) const;
+
+ /**
+ * Return whether the given accessible is focused or contains the focus or
+ * contained by focused accessible.
+ */
+ enum FocusDisposition {
+ eNone,
+ eFocused,
+ eContainsFocus,
+ eContainedByFocus
+ };
+ FocusDisposition IsInOrContainsFocus(const Accessible* aAccessible) const;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Focus notifications and processing (don't use until you know what you do).
+
+ /**
+ * Called when DOM focus event is fired.
+ */
+ void NotifyOfDOMFocus(nsISupports* aTarget);
+
+ /**
+ * Called when DOM blur event is fired.
+ */
+ void NotifyOfDOMBlur(nsISupports* aTarget);
+
+ /**
+ * Called when active item is changed. Note: must be called when accessible
+ * tree is up to date.
+ */
+ void ActiveItemChanged(Accessible* aItem, bool aCheckIfActive = true);
+
+ /**
+ * Dispatch delayed focus event for the current focus accessible.
+ */
+ void ForceFocusEvent();
+
+ /**
+ * Dispatch delayed focus event for the given target.
+ */
+ void DispatchFocusEvent(DocAccessible* aDocument, Accessible* aTarget);
+
+ /**
+ * Process DOM focus notification.
+ */
+ void ProcessDOMFocus(nsINode* aTarget);
+
+ /**
+ * Process the delayed accessible event.
+ * do.
+ */
+ void ProcessFocusEvent(AccEvent* aEvent);
+
+protected:
+ FocusManager();
+
+private:
+ FocusManager(const FocusManager&);
+ FocusManager& operator =(const FocusManager&);
+
+ /**
+ * Return DOM node having DOM focus.
+ */
+ nsINode* FocusedDOMNode() const;
+
+ /**
+ * Return DOM document having DOM focus.
+ */
+ nsIDocument* FocusedDOMDocument() const;
+
+private:
+ RefPtr<Accessible> mActiveItem;
+ RefPtr<Accessible> mActiveARIAMenubar;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/Logging.cpp b/accessible/base/Logging.cpp
new file mode 100644
index 000000000..afc37ef85
--- /dev/null
+++ b/accessible/base/Logging.cpp
@@ -0,0 +1,1039 @@
+/* -*- 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 "Logging.h"
+
+#include "Accessible-inl.h"
+#include "AccEvent.h"
+#include "DocAccessible.h"
+#include "nsAccessibilityService.h"
+#include "nsCoreUtils.h"
+#include "OuterDocAccessible.h"
+
+#include "nsDocShellLoadTypes.h"
+#include "nsIChannel.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsISelectionPrivate.h"
+#include "nsTraceRefcnt.h"
+#include "nsIWebProgress.h"
+#include "prenv.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIURI.h"
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// Logging helpers
+
+static uint32_t sModules = 0;
+
+struct ModuleRep {
+ const char* mStr;
+ logging::EModules mModule;
+};
+
+static ModuleRep sModuleMap[] = {
+ { "docload", logging::eDocLoad },
+ { "doccreate", logging::eDocCreate },
+ { "docdestroy", logging::eDocDestroy },
+ { "doclifecycle", logging::eDocLifeCycle },
+
+ { "events", logging::eEvents },
+ { "eventTree", logging::eEventTree },
+ { "platforms", logging::ePlatforms },
+ { "text", logging::eText },
+ { "tree", logging::eTree },
+
+ { "DOMEvents", logging::eDOMEvents },
+ { "focus", logging::eFocus },
+ { "selection", logging::eSelection },
+ { "notifications", logging::eNotifications },
+
+ { "stack", logging::eStack },
+ { "verbose", logging::eVerbose }
+};
+
+static void
+EnableLogging(const char* aModulesStr)
+{
+ sModules = 0;
+ if (!aModulesStr)
+ return;
+
+ const char* token = aModulesStr;
+ while (*token != '\0') {
+ size_t tokenLen = strcspn(token, ",");
+ for (unsigned int idx = 0; idx < ArrayLength(sModuleMap); idx++) {
+ if (strncmp(token, sModuleMap[idx].mStr, tokenLen) == 0) {
+#if !defined(MOZ_PROFILING) && (!defined(DEBUG) || defined(MOZ_OPTIMIZE))
+ // Stack tracing on profiling enabled or debug not optimized builds.
+ if (strncmp(token, "stack", tokenLen) == 0)
+ break;
+#endif
+ sModules |= sModuleMap[idx].mModule;
+ printf("\n\nmodule enabled: %s\n", sModuleMap[idx].mStr);
+ break;
+ }
+ }
+ token += tokenLen;
+
+ if (*token == ',')
+ token++; // skip ',' char
+ }
+}
+
+static void
+LogDocURI(nsIDocument* aDocumentNode)
+{
+ printf("uri: %s", aDocumentNode->GetDocumentURI()->GetSpecOrDefault().get());
+}
+
+static void
+LogDocShellState(nsIDocument* aDocumentNode)
+{
+ printf("docshell busy: ");
+
+ nsAutoCString docShellBusy;
+ nsCOMPtr<nsIDocShell> docShell = aDocumentNode->GetDocShell();
+ uint32_t busyFlags = nsIDocShell::BUSY_FLAGS_NONE;
+ docShell->GetBusyFlags(&busyFlags);
+ if (busyFlags == nsIDocShell::BUSY_FLAGS_NONE) {
+ printf("'none'");
+ }
+ if (busyFlags & nsIDocShell::BUSY_FLAGS_BUSY) {
+ printf("'busy'");
+ }
+ if (busyFlags & nsIDocShell::BUSY_FLAGS_BEFORE_PAGE_LOAD) {
+ printf(", 'before page load'");
+ }
+ if (busyFlags & nsIDocShell::BUSY_FLAGS_PAGE_LOADING) {
+ printf(", 'page loading'");
+ }
+}
+
+static void
+LogDocType(nsIDocument* aDocumentNode)
+{
+ if (aDocumentNode->IsActive()) {
+ bool isContent = nsCoreUtils::IsContentDocument(aDocumentNode);
+ printf("%s document", (isContent ? "content" : "chrome"));
+ } else {
+ printf("document type: [failed]");\
+ }
+}
+
+static void
+LogDocShellTree(nsIDocument* aDocumentNode)
+{
+ if (aDocumentNode->IsActive()) {
+ nsCOMPtr<nsIDocShellTreeItem> treeItem(aDocumentNode->GetDocShell());
+ nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
+ treeItem->GetParent(getter_AddRefs(parentTreeItem));
+ nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
+ treeItem->GetRootTreeItem(getter_AddRefs(rootTreeItem));
+ printf("docshell hierarchy, parent: %p, root: %p, is tab document: %s;",
+ static_cast<void*>(parentTreeItem), static_cast<void*>(rootTreeItem),
+ (nsCoreUtils::IsTabDocument(aDocumentNode) ? "yes" : "no"));
+ }
+}
+
+static void
+LogDocState(nsIDocument* aDocumentNode)
+{
+ const char* docState = nullptr;
+ nsIDocument::ReadyState docStateFlag = aDocumentNode->GetReadyStateEnum();
+ switch (docStateFlag) {
+ case nsIDocument::READYSTATE_UNINITIALIZED:
+ docState = "uninitialized";
+ break;
+ case nsIDocument::READYSTATE_LOADING:
+ docState = "loading";
+ break;
+ case nsIDocument::READYSTATE_INTERACTIVE:
+ docState = "interactive";
+ break;
+ case nsIDocument::READYSTATE_COMPLETE:
+ docState = "complete";
+ break;
+ }
+
+ printf("doc state: %s", docState);
+ printf(", %sinitial", aDocumentNode->IsInitialDocument() ? "" : "not ");
+ printf(", %sshowing", aDocumentNode->IsShowing() ? "" : "not ");
+ printf(", %svisible", aDocumentNode->IsVisible() ? "" : "not ");
+ printf(", %svisible considering ancestors", aDocumentNode->IsVisibleConsideringAncestors() ? "" : "not ");
+ printf(", %sactive", aDocumentNode->IsActive() ? "" : "not ");
+ printf(", %sresource", aDocumentNode->IsResourceDoc() ? "" : "not ");
+
+ dom::Element* rootEl = aDocumentNode->GetBodyElement();
+ if (!rootEl) {
+ rootEl = aDocumentNode->GetRootElement();
+ }
+ printf(", has %srole content", rootEl ? "" : "no ");
+}
+
+static void
+LogPresShell(nsIDocument* aDocumentNode)
+{
+ nsIPresShell* ps = aDocumentNode->GetShell();
+ printf("presshell: %p", static_cast<void*>(ps));
+
+ nsIScrollableFrame* sf = nullptr;
+ if (ps) {
+ printf(", is %s destroying", (ps->IsDestroying() ? "" : "not"));
+ sf = ps->GetRootScrollFrameAsScrollable();
+ }
+ printf(", root scroll frame: %p", static_cast<void*>(sf));
+}
+
+static void
+LogDocLoadGroup(nsIDocument* aDocumentNode)
+{
+ nsCOMPtr<nsILoadGroup> loadGroup = aDocumentNode->GetDocumentLoadGroup();
+ printf("load group: %p", static_cast<void*>(loadGroup));
+}
+
+static void
+LogDocParent(nsIDocument* aDocumentNode)
+{
+ nsIDocument* parentDoc = aDocumentNode->GetParentDocument();
+ printf("parent DOM document: %p", static_cast<void*>(parentDoc));
+ if (parentDoc) {
+ printf(", parent acc document: %p",
+ static_cast<void*>(GetExistingDocAccessible(parentDoc)));
+ printf("\n parent ");
+ LogDocURI(parentDoc);
+ printf("\n");
+ }
+}
+
+static void
+LogDocInfo(nsIDocument* aDocumentNode, DocAccessible* aDocument)
+{
+ printf(" DOM document: %p, acc document: %p\n ",
+ static_cast<void*>(aDocumentNode), static_cast<void*>(aDocument));
+
+ // log document info
+ if (aDocumentNode) {
+ LogDocURI(aDocumentNode);
+ printf("\n ");
+ LogDocShellState(aDocumentNode);
+ printf("; ");
+ LogDocType(aDocumentNode);
+ printf("\n ");
+ LogDocShellTree(aDocumentNode);
+ printf("\n ");
+ LogDocState(aDocumentNode);
+ printf("\n ");
+ LogPresShell(aDocumentNode);
+ printf("\n ");
+ LogDocLoadGroup(aDocumentNode);
+ printf(", ");
+ LogDocParent(aDocumentNode);
+ printf("\n");
+ }
+}
+
+static void
+LogShellLoadType(nsIDocShell* aDocShell)
+{
+ printf("load type: ");
+
+ uint32_t loadType = 0;
+ aDocShell->GetLoadType(&loadType);
+ switch (loadType) {
+ case LOAD_NORMAL:
+ printf("normal; ");
+ break;
+ case LOAD_NORMAL_REPLACE:
+ printf("normal replace; ");
+ break;
+ case LOAD_NORMAL_EXTERNAL:
+ printf("normal external; ");
+ break;
+ case LOAD_HISTORY:
+ printf("history; ");
+ break;
+ case LOAD_NORMAL_BYPASS_CACHE:
+ printf("normal bypass cache; ");
+ break;
+ case LOAD_NORMAL_BYPASS_PROXY:
+ printf("normal bypass proxy; ");
+ break;
+ case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
+ printf("normal bypass proxy and cache; ");
+ break;
+ case LOAD_NORMAL_ALLOW_MIXED_CONTENT:
+ printf("normal allow mixed content; ");
+ break;
+ case LOAD_RELOAD_NORMAL:
+ printf("reload normal; ");
+ break;
+ case LOAD_RELOAD_BYPASS_CACHE:
+ printf("reload bypass cache; ");
+ break;
+ case LOAD_RELOAD_BYPASS_PROXY:
+ printf("reload bypass proxy; ");
+ break;
+ case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
+ printf("reload bypass proxy and cache; ");
+ break;
+ case LOAD_RELOAD_ALLOW_MIXED_CONTENT:
+ printf("reload allow mixed content; ");
+ break;
+ case LOAD_LINK:
+ printf("link; ");
+ break;
+ case LOAD_REFRESH:
+ printf("refresh; ");
+ break;
+ case LOAD_RELOAD_CHARSET_CHANGE:
+ printf("reload charset change; ");
+ break;
+ case LOAD_BYPASS_HISTORY:
+ printf("bypass history; ");
+ break;
+ case LOAD_STOP_CONTENT:
+ printf("stop content; ");
+ break;
+ case LOAD_STOP_CONTENT_AND_REPLACE:
+ printf("stop content and replace; ");
+ break;
+ case LOAD_PUSHSTATE:
+ printf("load pushstate; ");
+ break;
+ case LOAD_REPLACE_BYPASS_CACHE:
+ printf("replace bypass cache; ");
+ break;
+ case LOAD_ERROR_PAGE:
+ printf("error page;");
+ break;
+ default:
+ printf("unknown");
+ }
+}
+
+static void
+LogRequest(nsIRequest* aRequest)
+{
+ if (aRequest) {
+ nsAutoCString name;
+ aRequest->GetName(name);
+ printf(" request spec: %s\n", name.get());
+ uint32_t loadFlags = 0;
+ aRequest->GetLoadFlags(&loadFlags);
+ printf(" request load flags: %x; ", loadFlags);
+ if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI)
+ printf("document uri; ");
+ if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI)
+ printf("retargeted document uri; ");
+ if (loadFlags & nsIChannel::LOAD_REPLACE)
+ printf("replace; ");
+ if (loadFlags & nsIChannel::LOAD_INITIAL_DOCUMENT_URI)
+ printf("initial document uri; ");
+ if (loadFlags & nsIChannel::LOAD_TARGETED)
+ printf("targeted; ");
+ if (loadFlags & nsIChannel::LOAD_CALL_CONTENT_SNIFFERS)
+ printf("call content sniffers; ");
+ if (loadFlags & nsIChannel::LOAD_CLASSIFY_URI)
+ printf("classify uri; ");
+ } else {
+ printf(" no request");
+ }
+}
+
+static void
+LogDocAccState(DocAccessible* aDocument)
+{
+ printf("document acc state: ");
+ if (aDocument->HasLoadState(DocAccessible::eCompletelyLoaded))
+ printf("completely loaded;");
+ else if (aDocument->HasLoadState(DocAccessible::eReady))
+ printf("ready;");
+ else if (aDocument->HasLoadState(DocAccessible::eDOMLoaded))
+ printf("DOM loaded;");
+ else if (aDocument->HasLoadState(DocAccessible::eTreeConstructed))
+ printf("tree constructed;");
+}
+
+static void
+GetDocLoadEventType(AccEvent* aEvent, nsACString& aEventType)
+{
+ uint32_t type = aEvent->GetEventType();
+ if (type == nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED) {
+ aEventType.AssignLiteral("load stopped");
+ } else if (type == nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE) {
+ aEventType.AssignLiteral("load complete");
+ } else if (type == nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD) {
+ aEventType.AssignLiteral("reload");
+ } else if (type == nsIAccessibleEvent::EVENT_STATE_CHANGE) {
+ AccStateChangeEvent* event = downcast_accEvent(aEvent);
+ if (event->GetState() == states::BUSY) {
+ aEventType.AssignLiteral("busy ");
+ if (event->IsStateEnabled())
+ aEventType.AppendLiteral("true");
+ else
+ aEventType.AppendLiteral("false");
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// namespace logging:: document life cycle logging methods
+
+static const char* sDocLoadTitle = "DOCLOAD";
+static const char* sDocCreateTitle = "DOCCREATE";
+static const char* sDocDestroyTitle = "DOCDESTROY";
+static const char* sDocEventTitle = "DOCEVENT";
+static const char* sFocusTitle = "FOCUS";
+
+void
+logging::DocLoad(const char* aMsg, nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, uint32_t aStateFlags)
+{
+ MsgBegin(sDocLoadTitle, aMsg);
+
+ nsCOMPtr<mozIDOMWindowProxy> DOMWindow;
+ aWebProgress->GetDOMWindow(getter_AddRefs(DOMWindow));
+ nsPIDOMWindowOuter* window = nsPIDOMWindowOuter::From(DOMWindow);
+ if (!window) {
+ MsgEnd();
+ return;
+ }
+
+ nsCOMPtr<nsIDocument> documentNode = window->GetDoc();
+ if (!documentNode) {
+ MsgEnd();
+ return;
+ }
+
+ DocAccessible* document = GetExistingDocAccessible(documentNode);
+
+ LogDocInfo(documentNode, document);
+
+ nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
+ printf("\n ");
+ LogShellLoadType(docShell);
+ printf("\n");
+ LogRequest(aRequest);
+ printf("\n");
+ printf(" state flags: %x", aStateFlags);
+ bool isDocLoading;
+ aWebProgress->GetIsLoadingDocument(&isDocLoading);
+ printf(", document is %sloading\n", (isDocLoading ? "" : "not "));
+
+ MsgEnd();
+}
+
+void
+logging::DocLoad(const char* aMsg, nsIDocument* aDocumentNode)
+{
+ MsgBegin(sDocLoadTitle, aMsg);
+
+ DocAccessible* document = GetExistingDocAccessible(aDocumentNode);
+ LogDocInfo(aDocumentNode, document);
+
+ MsgEnd();
+}
+
+void
+logging::DocCompleteLoad(DocAccessible* aDocument, bool aIsLoadEventTarget)
+{
+ MsgBegin(sDocLoadTitle, "document loaded *completely*");
+
+ printf(" DOM document: %p, acc document: %p\n",
+ static_cast<void*>(aDocument->DocumentNode()),
+ static_cast<void*>(aDocument));
+
+ printf(" ");
+ LogDocURI(aDocument->DocumentNode());
+ printf("\n");
+
+ printf(" ");
+ LogDocAccState(aDocument);
+ printf("\n");
+
+ printf(" document is load event target: %s\n",
+ (aIsLoadEventTarget ? "true" : "false"));
+
+ MsgEnd();
+}
+
+void
+logging::DocLoadEventFired(AccEvent* aEvent)
+{
+ nsAutoCString strEventType;
+ GetDocLoadEventType(aEvent, strEventType);
+ if (!strEventType.IsEmpty())
+ printf(" fire: %s\n", strEventType.get());
+}
+
+void
+logging::DocLoadEventHandled(AccEvent* aEvent)
+{
+ nsAutoCString strEventType;
+ GetDocLoadEventType(aEvent, strEventType);
+ if (strEventType.IsEmpty())
+ return;
+
+ MsgBegin(sDocEventTitle, "handled '%s' event", strEventType.get());
+
+ DocAccessible* document = aEvent->GetAccessible()->AsDoc();
+ if (document)
+ LogDocInfo(document->DocumentNode(), document);
+
+ MsgEnd();
+}
+
+void
+logging::DocCreate(const char* aMsg, nsIDocument* aDocumentNode,
+ DocAccessible* aDocument)
+{
+ DocAccessible* document = aDocument ?
+ aDocument : GetExistingDocAccessible(aDocumentNode);
+
+ MsgBegin(sDocCreateTitle, aMsg);
+ LogDocInfo(aDocumentNode, document);
+ MsgEnd();
+}
+
+void
+logging::DocDestroy(const char* aMsg, nsIDocument* aDocumentNode,
+ DocAccessible* aDocument)
+{
+ DocAccessible* document = aDocument ?
+ aDocument : GetExistingDocAccessible(aDocumentNode);
+
+ MsgBegin(sDocDestroyTitle, aMsg);
+ LogDocInfo(aDocumentNode, document);
+ MsgEnd();
+}
+
+void
+logging::OuterDocDestroy(OuterDocAccessible* aOuterDoc)
+{
+ MsgBegin(sDocDestroyTitle, "outerdoc shutdown");
+ logging::Address("outerdoc", aOuterDoc);
+ MsgEnd();
+}
+
+void
+logging::FocusNotificationTarget(const char* aMsg, const char* aTargetDescr,
+ Accessible* aTarget)
+{
+ MsgBegin(sFocusTitle, aMsg);
+ AccessibleNNode(aTargetDescr, aTarget);
+ MsgEnd();
+}
+
+void
+logging::FocusNotificationTarget(const char* aMsg, const char* aTargetDescr,
+ nsINode* aTargetNode)
+{
+ MsgBegin(sFocusTitle, aMsg);
+ Node(aTargetDescr, aTargetNode);
+ MsgEnd();
+}
+
+void
+logging::FocusNotificationTarget(const char* aMsg, const char* aTargetDescr,
+ nsISupports* aTargetThing)
+{
+ MsgBegin(sFocusTitle, aMsg);
+
+ if (aTargetThing) {
+ nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTargetThing));
+ if (targetNode)
+ AccessibleNNode(aTargetDescr, targetNode);
+ else
+ printf(" %s: %p, window\n", aTargetDescr,
+ static_cast<void*>(aTargetThing));
+ }
+
+ MsgEnd();
+}
+
+void
+logging::ActiveItemChangeCausedBy(const char* aCause, Accessible* aTarget)
+{
+ SubMsgBegin();
+ printf(" Caused by: %s\n", aCause);
+ AccessibleNNode("Item", aTarget);
+ SubMsgEnd();
+}
+
+void
+logging::ActiveWidget(Accessible* aWidget)
+{
+ SubMsgBegin();
+
+ AccessibleNNode("Widget", aWidget);
+ printf(" Widget is active: %s, has operable items: %s\n",
+ (aWidget && aWidget->IsActiveWidget() ? "true" : "false"),
+ (aWidget && aWidget->AreItemsOperable() ? "true" : "false"));
+
+ SubMsgEnd();
+}
+
+void
+logging::FocusDispatched(Accessible* aTarget)
+{
+ SubMsgBegin();
+ AccessibleNNode("A11y target", aTarget);
+ SubMsgEnd();
+}
+
+void
+logging::SelChange(nsISelection* aSelection, DocAccessible* aDocument,
+ int16_t aReason)
+{
+ nsCOMPtr<nsISelectionPrivate> privSel(do_QueryInterface(aSelection));
+
+ int16_t type = 0;
+ privSel->GetType(&type);
+
+ const char* strType = 0;
+ if (type == nsISelectionController::SELECTION_NORMAL)
+ strType = "normal";
+ else if (type == nsISelectionController::SELECTION_SPELLCHECK)
+ strType = "spellcheck";
+ else
+ strType = "unknown";
+
+ bool isIgnored = !aDocument || !aDocument->IsContentLoaded();
+ printf("\nSelection changed, selection type: %s, notification %s, reason: %d\n",
+ strType, (isIgnored ? "ignored" : "pending"), aReason);
+
+ Stack();
+}
+
+void
+logging::TreeInfo(const char* aMsg, uint32_t aExtraFlags, ...)
+{
+ if (IsEnabledAll(logging::eTree | aExtraFlags)) {
+ va_list vl;
+ va_start(vl, aExtraFlags);
+ const char* descr = va_arg(vl, const char*);
+ if (descr) {
+ Accessible* acc = va_arg(vl, Accessible*);
+ MsgBegin("TREE", "%s; doc: %p", aMsg, acc ? acc->Document() : nullptr);
+ AccessibleInfo(descr, acc);
+ while ((descr = va_arg(vl, const char*))) {
+ AccessibleInfo(descr, va_arg(vl, Accessible*));
+ }
+ }
+ else {
+ MsgBegin("TREE", aMsg);
+ }
+ va_end(vl);
+ MsgEnd();
+
+ if (aExtraFlags & eStack) {
+ Stack();
+ }
+ }
+}
+
+void
+logging::TreeInfo(const char* aMsg, uint32_t aExtraFlags,
+ const char* aMsg1, Accessible* aAcc,
+ const char* aMsg2, nsINode* aNode)
+{
+ if (IsEnabledAll(logging::eTree | aExtraFlags)) {
+ MsgBegin("TREE", "%s; doc: %p", aMsg, aAcc ? aAcc->Document() : nullptr);
+ AccessibleInfo(aMsg1, aAcc);
+ Accessible* acc = aAcc ? aAcc->Document()->GetAccessible(aNode) : nullptr;
+ if (acc) {
+ AccessibleInfo(aMsg2, acc);
+ }
+ else {
+ Node(aMsg2, aNode);
+ }
+ MsgEnd();
+ }
+}
+
+
+void
+logging::TreeInfo(const char* aMsg, uint32_t aExtraFlags, Accessible* aParent)
+{
+ if (IsEnabledAll(logging::eTree | aExtraFlags)) {
+ MsgBegin("TREE", "%s; doc: %p", aMsg, aParent->Document());
+ AccessibleInfo("container", aParent);
+ for (uint32_t idx = 0; idx < aParent->ChildCount(); idx++) {
+ AccessibleInfo("child", aParent->GetChildAt(idx));
+ }
+ MsgEnd();
+ }
+}
+
+void
+logging::Tree(const char* aTitle, const char* aMsgText,
+ Accessible* aRoot, GetTreePrefix aPrefixFunc,
+ void* aGetTreePrefixData)
+{
+ logging::MsgBegin(aTitle, aMsgText);
+
+ nsAutoString level;
+ Accessible* root = aRoot;
+ do {
+ const char* prefix = aPrefixFunc ? aPrefixFunc(aGetTreePrefixData, root) : "";
+ printf("%s", NS_ConvertUTF16toUTF8(level).get());
+ logging::AccessibleInfo(prefix, root);
+ if (root->FirstChild() && !root->FirstChild()->IsDoc()) {
+ level.Append(NS_LITERAL_STRING(" "));
+ root = root->FirstChild();
+ continue;
+ }
+ int32_t idxInParent = root != aRoot && root->mParent ?
+ root->mParent->mChildren.IndexOf(root) : -1;
+ if (idxInParent != -1 &&
+ idxInParent < static_cast<int32_t>(root->mParent->mChildren.Length() - 1)) {
+ root = root->mParent->mChildren.ElementAt(idxInParent + 1);
+ continue;
+ }
+ while (root != aRoot && (root = root->Parent())) {
+ level.Cut(0, 2);
+ int32_t idxInParent = !root->IsDoc() && root->mParent ?
+ root->mParent->mChildren.IndexOf(root) : -1;
+ if (idxInParent != -1 &&
+ idxInParent < static_cast<int32_t>(root->mParent->mChildren.Length() - 1)) {
+ root = root->mParent->mChildren.ElementAt(idxInParent + 1);
+ break;
+ }
+ }
+ }
+ while (root && root != aRoot);
+
+ logging::MsgEnd();
+}
+
+void
+logging::DOMTree(const char* aTitle, const char* aMsgText,
+ DocAccessible* aDocument)
+{
+ logging::MsgBegin(aTitle, aMsgText);
+ nsAutoString level;
+ nsINode* root = aDocument->DocumentNode();
+ do {
+ printf("%s", NS_ConvertUTF16toUTF8(level).get());
+ logging::Node("", root);
+ if (root->GetFirstChild()) {
+ level.Append(NS_LITERAL_STRING(" "));
+ root = root->GetFirstChild();
+ continue;
+ }
+ if (root->GetNextSibling()) {
+ root = root->GetNextSibling();
+ continue;
+ }
+ while ((root = root->GetParentNode())) {
+ level.Cut(0, 2);
+ if (root->GetNextSibling()) {
+ root = root->GetNextSibling();
+ break;
+ }
+ }
+ }
+ while (root);
+ logging::MsgEnd();
+}
+
+void
+logging::MsgBegin(const char* aTitle, const char* aMsgText, ...)
+{
+ printf("\nA11Y %s: ", aTitle);
+
+ va_list argptr;
+ va_start(argptr, aMsgText);
+ vprintf(aMsgText, argptr);
+ va_end(argptr);
+
+ PRIntervalTime time = PR_IntervalNow();
+ uint32_t mins = (PR_IntervalToSeconds(time) / 60) % 60;
+ uint32_t secs = PR_IntervalToSeconds(time) % 60;
+ uint32_t msecs = PR_IntervalToMilliseconds(time) % 1000;
+ printf("; %02d:%02d.%03d", mins, secs, msecs);
+
+ printf("\n {\n");
+}
+
+void
+logging::MsgEnd()
+{
+ printf(" }\n");
+}
+
+void
+logging::SubMsgBegin()
+{
+ printf(" {\n");
+}
+
+void
+logging::SubMsgEnd()
+{
+ printf(" }\n");
+}
+
+void
+logging::MsgEntry(const char* aEntryText, ...)
+{
+ printf(" ");
+
+ va_list argptr;
+ va_start(argptr, aEntryText);
+ vprintf(aEntryText, argptr);
+ va_end(argptr);
+
+ printf("\n");
+}
+
+void
+logging::Text(const char* aText)
+{
+ printf(" %s\n", aText);
+}
+
+void
+logging::Address(const char* aDescr, Accessible* aAcc)
+{
+ if (!aAcc->IsDoc()) {
+ printf(" %s accessible: %p, node: %p\n", aDescr,
+ static_cast<void*>(aAcc), static_cast<void*>(aAcc->GetNode()));
+ }
+
+ DocAccessible* doc = aAcc->Document();
+ nsIDocument* docNode = doc->DocumentNode();
+ printf(" document: %p, node: %p\n",
+ static_cast<void*>(doc), static_cast<void*>(docNode));
+
+ printf(" ");
+ LogDocURI(docNode);
+ printf("\n");
+}
+
+void
+logging::Node(const char* aDescr, nsINode* aNode)
+{
+ printf(" ");
+
+ if (!aNode) {
+ printf("%s: null\n", aDescr);
+ return;
+ }
+
+ if (aNode->IsNodeOfType(nsINode::eDOCUMENT)) {
+ printf("%s: %p, document\n", aDescr, static_cast<void*>(aNode));
+ return;
+ }
+
+ nsINode* parentNode = aNode->GetParentNode();
+ int32_t idxInParent = parentNode ? parentNode->IndexOf(aNode) : - 1;
+
+ if (aNode->IsNodeOfType(nsINode::eTEXT)) {
+ printf("%s: %p, text node, idx in parent: %d\n",
+ aDescr, static_cast<void*>(aNode), idxInParent);
+ return;
+ }
+
+ if (!aNode->IsElement()) {
+ printf("%s: %p, not accessible node type, idx in parent: %d\n",
+ aDescr, static_cast<void*>(aNode), idxInParent);
+ return;
+ }
+
+ dom::Element* elm = aNode->AsElement();
+
+ nsAutoCString tag;
+ elm->NodeInfo()->NameAtom()->ToUTF8String(tag);
+
+ nsIAtom* idAtom = elm->GetID();
+ nsAutoCString id;
+ if (idAtom)
+ idAtom->ToUTF8String(id);
+
+ printf("%s: %p, %s@id='%s', idx in parent: %d\n",
+ aDescr, static_cast<void*>(elm), tag.get(), id.get(), idxInParent);
+}
+
+void
+logging::Document(DocAccessible* aDocument)
+{
+ printf(" Document: %p, document node: %p\n",
+ static_cast<void*>(aDocument),
+ static_cast<void*>(aDocument->DocumentNode()));
+
+ printf(" Document ");
+ LogDocURI(aDocument->DocumentNode());
+ printf("\n");
+}
+
+void
+logging::AccessibleInfo(const char* aDescr, Accessible* aAccessible)
+{
+ printf(" %s: %p; ", aDescr, static_cast<void*>(aAccessible));
+ if (!aAccessible) {
+ printf("\n");
+ return;
+ }
+ if (aAccessible->IsDefunct()) {
+ printf("defunct\n");
+ return;
+ }
+ if (!aAccessible->Document() || aAccessible->Document()->IsDefunct()) {
+ printf("document is shutting down, no info\n");
+ return;
+ }
+
+ nsAutoString role;
+ GetAccService()->GetStringRole(aAccessible->Role(), role);
+ printf("role: %s", NS_ConvertUTF16toUTF8(role).get());
+
+ nsAutoString name;
+ aAccessible->Name(name);
+ if (!name.IsEmpty()) {
+ printf(", name: '%s'", NS_ConvertUTF16toUTF8(name).get());
+ }
+
+ printf(", idx: %d", aAccessible->IndexInParent());
+
+ nsINode* node = aAccessible->GetNode();
+ if (!node) {
+ printf(", node: null\n");
+ }
+ else if (node->IsNodeOfType(nsINode::eDOCUMENT)) {
+ printf(", document node: %p\n", static_cast<void*>(node));
+ }
+ else if (node->IsNodeOfType(nsINode::eTEXT)) {
+ printf(", text node: %p\n", static_cast<void*>(node));
+ }
+ else if (node->IsElement()) {
+ dom::Element* el = node->AsElement();
+
+ nsAutoCString tag;
+ el->NodeInfo()->NameAtom()->ToUTF8String(tag);
+
+ nsIAtom* idAtom = el->GetID();
+ nsAutoCString id;
+ if (idAtom) {
+ idAtom->ToUTF8String(id);
+ }
+
+ printf(", element node: %p, %s@id='%s'\n",
+ static_cast<void*>(el), tag.get(), id.get());
+ }
+}
+
+void
+logging::AccessibleNNode(const char* aDescr, Accessible* aAccessible)
+{
+ printf(" %s: %p; ", aDescr, static_cast<void*>(aAccessible));
+ if (!aAccessible)
+ return;
+
+ nsAutoString role;
+ GetAccService()->GetStringRole(aAccessible->Role(), role);
+ nsAutoString name;
+ aAccessible->Name(name);
+
+ printf("role: %s, name: '%s';\n", NS_ConvertUTF16toUTF8(role).get(),
+ NS_ConvertUTF16toUTF8(name).get());
+
+ nsAutoCString nodeDescr(aDescr);
+ nodeDescr.AppendLiteral(" node");
+ Node(nodeDescr.get(), aAccessible->GetNode());
+
+ Document(aAccessible->Document());
+}
+
+void
+logging::AccessibleNNode(const char* aDescr, nsINode* aNode)
+{
+ DocAccessible* document =
+ GetAccService()->GetDocAccessible(aNode->OwnerDoc());
+
+ if (document) {
+ Accessible* accessible = document->GetAccessible(aNode);
+ if (accessible) {
+ AccessibleNNode(aDescr, accessible);
+ return;
+ }
+ }
+
+ nsAutoCString nodeDescr("[not accessible] ");
+ nodeDescr.Append(aDescr);
+ Node(nodeDescr.get(), aNode);
+
+ if (document) {
+ Document(document);
+ return;
+ }
+
+ printf(" [contained by not accessible document]:\n");
+ LogDocInfo(aNode->OwnerDoc(), document);
+ printf("\n");
+}
+
+void
+logging::DOMEvent(const char* aDescr, nsINode* aOrigTarget,
+ const nsAString& aEventType)
+{
+ logging::MsgBegin("DOMEvents", "event '%s' %s",
+ NS_ConvertUTF16toUTF8(aEventType).get(), aDescr);
+ logging::AccessibleNNode("Target", aOrigTarget);
+ logging::MsgEnd();
+}
+
+void
+logging::Stack()
+{
+ if (IsEnabled(eStack)) {
+ printf(" stack: \n");
+ nsTraceRefcnt::WalkTheStack(stdout);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// namespace logging:: initialization
+
+bool
+logging::IsEnabled(uint32_t aModules)
+{
+ return sModules & aModules;
+}
+
+bool
+logging::IsEnabledAll(uint32_t aModules)
+{
+ return (sModules & aModules) == aModules;
+}
+
+bool
+logging::IsEnabled(const nsAString& aModuleStr)
+{
+ for (unsigned int idx = 0; idx < ArrayLength(sModuleMap); idx++) {
+ if (aModuleStr.EqualsASCII(sModuleMap[idx].mStr))
+ return sModules & sModuleMap[idx].mModule;
+ }
+
+ return false;
+}
+
+void
+logging::Enable(const nsAFlatCString& aModules)
+{
+ EnableLogging(aModules.get());
+}
+
+
+void
+logging::CheckEnv()
+{
+ EnableLogging(PR_GetEnv("A11YLOG"));
+}
diff --git a/accessible/base/Logging.h b/accessible/base/Logging.h
new file mode 100644
index 000000000..a5d2c16b2
--- /dev/null
+++ b/accessible/base/Logging.h
@@ -0,0 +1,225 @@
+/* -*- 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_logs_h__
+#define mozilla_a11y_logs_h__
+
+#include "nscore.h"
+#include "nsStringFwd.h"
+
+class nsIDocument;
+class nsINode;
+class nsIRequest;
+class nsISelection;
+class nsISupports;
+class nsIWebProgress;
+
+namespace mozilla {
+namespace a11y {
+
+class AccEvent;
+class Accessible;
+class DocAccessible;
+class OuterDocAccessible;
+
+namespace logging {
+
+enum EModules {
+ eDocLoad = 1 << 0,
+ eDocCreate = 1 << 1,
+ eDocDestroy = 1 << 2,
+ eDocLifeCycle = eDocLoad | eDocCreate | eDocDestroy,
+
+ eEvents = 1 << 3,
+ eEventTree = 1 << 4,
+ ePlatforms = 1 << 5,
+ eText = 1 << 6,
+ eTree = 1 << 7,
+
+ eDOMEvents = 1 << 8,
+ eFocus = 1 << 9,
+ eSelection = 1 << 10,
+ eNotifications = eDOMEvents | eSelection | eFocus,
+
+ // extras
+ eStack = 1 << 11,
+ eVerbose = 1 << 12
+};
+
+/**
+ * Return true if any of the given modules is logged.
+ */
+bool IsEnabled(uint32_t aModules);
+
+/**
+ * Return true if all of the given modules are logged.
+ */
+bool IsEnabledAll(uint32_t aModules);
+
+/**
+ * Return true if the given module is logged.
+ */
+bool IsEnabled(const nsAString& aModules);
+
+/**
+ * Log the document loading progress.
+ */
+void DocLoad(const char* aMsg, nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, uint32_t aStateFlags);
+void DocLoad(const char* aMsg, nsIDocument* aDocumentNode);
+void DocCompleteLoad(DocAccessible* aDocument, bool aIsLoadEventTarget);
+
+/**
+ * Log that document load event was fired.
+ */
+void DocLoadEventFired(AccEvent* aEvent);
+
+/**
+ * Log that document laod event was handled.
+ */
+void DocLoadEventHandled(AccEvent* aEvent);
+
+/**
+ * Log the document was created.
+ */
+void DocCreate(const char* aMsg, nsIDocument* aDocumentNode,
+ DocAccessible* aDocument = nullptr);
+
+/**
+ * Log the document was destroyed.
+ */
+void DocDestroy(const char* aMsg, nsIDocument* aDocumentNode,
+ DocAccessible* aDocument = nullptr);
+
+/**
+ * Log the outer document was destroyed.
+ */
+void OuterDocDestroy(OuterDocAccessible* OuterDoc);
+
+/**
+ * Log the focus notification target.
+ */
+void FocusNotificationTarget(const char* aMsg, const char* aTargetDescr,
+ Accessible* aTarget);
+void FocusNotificationTarget(const char* aMsg, const char* aTargetDescr,
+ nsINode* aTargetNode);
+void FocusNotificationTarget(const char* aMsg, const char* aTargetDescr,
+ nsISupports* aTargetThing);
+
+/**
+ * Log a cause of active item descendant change (submessage).
+ */
+void ActiveItemChangeCausedBy(const char* aMsg, Accessible* aTarget);
+
+/**
+ * Log the active widget (submessage).
+ */
+void ActiveWidget(Accessible* aWidget);
+
+/**
+ * Log the focus event was dispatched (submessage).
+ */
+void FocusDispatched(Accessible* aTarget);
+
+/**
+ * Log the selection change.
+ */
+void SelChange(nsISelection* aSelection, DocAccessible* aDocument,
+ int16_t aReason);
+
+/**
+ * Log the given accessible elements info.
+ */
+void TreeInfo(const char* aMsg, uint32_t aExtraFlags, ...);
+void TreeInfo(const char* aMsg, uint32_t aExtraFlags,
+ const char* aMsg1, Accessible* aAcc,
+ const char* aMsg2, nsINode* aNode);
+void TreeInfo(const char* aMsg, uint32_t aExtraFlags, Accessible* aParent);
+
+/**
+ * Log the accessible/DOM tree.
+ */
+typedef const char* (*GetTreePrefix)(void* aData, Accessible*);
+void Tree(const char* aTitle, const char* aMsgText, Accessible* aRoot,
+ GetTreePrefix aPrefixFunc = nullptr, void* aGetTreePrefixData = nullptr);
+void DOMTree(const char* aTitle, const char* aMsgText, DocAccessible* aDoc);
+
+/**
+ * Log the message ('title: text' format) on new line. Print the start and end
+ * boundaries of the message body designated by '{' and '}' (2 spaces indent for
+ * body).
+ */
+void MsgBegin(const char* aTitle, const char* aMsgText, ...);
+void MsgEnd();
+
+/**
+ * Print start and end boundaries of the message body designated by '{' and '}'
+ * (2 spaces indent for body).
+ */
+void SubMsgBegin();
+void SubMsgEnd();
+
+/**
+ * Log the entry into message body (4 spaces indent).
+ */
+void MsgEntry(const char* aEntryText, ...);
+
+/**
+ * Log the text, two spaces offset is used.
+ */
+void Text(const char* aText);
+
+/**
+ * Log the accessible object address as message entry (4 spaces indent).
+ */
+void Address(const char* aDescr, Accessible* aAcc);
+
+/**
+ * Log the DOM node info as message entry.
+ */
+void Node(const char* aDescr, nsINode* aNode);
+
+/**
+ * Log the document accessible info as message entry.
+ */
+void Document(DocAccessible* aDocument);
+
+/**
+ * Log the accessible and its DOM node as a message entry.
+ */
+void AccessibleInfo(const char* aDescr, Accessible* aAccessible);
+void AccessibleNNode(const char* aDescr, Accessible* aAccessible);
+void AccessibleNNode(const char* aDescr, nsINode* aNode);
+
+/**
+ * Log the DOM event.
+ */
+void DOMEvent(const char* aDescr, nsINode* aOrigTarget,
+ const nsAString& aEventType);
+
+/**
+ * Log the call stack, two spaces offset is used.
+ */
+void Stack();
+
+/**
+ * Enable logging of the specified modules, all other modules aren't logged.
+ */
+void Enable(const nsAFlatCString& aModules);
+
+/**
+ * Enable logging of modules specified by A11YLOG environment variable,
+ * all other modules aren't logged.
+ */
+void CheckEnv();
+
+} // namespace logging
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/base/MarkupMap.h b/accessible/base/MarkupMap.h
new file mode 100644
index 000000000..5ba1a06e6
--- /dev/null
+++ b/accessible/base/MarkupMap.h
@@ -0,0 +1,340 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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/. */
+
+MARKUPMAP(a,
+ New_HTMLLink,
+ roles::LINK)
+
+MARKUPMAP(abbr,
+ New_HyperText,
+ 0)
+
+MARKUPMAP(acronym,
+ New_HyperText,
+ 0)
+
+MARKUPMAP(article,
+ New_HyperText,
+ roles::DOCUMENT,
+ Attr(xmlroles, article))
+
+MARKUPMAP(aside,
+ New_HyperText,
+ roles::NOTE)
+
+MARKUPMAP(blockquote,
+ New_HyperText,
+ roles::SECTION)
+
+MARKUPMAP(dd,
+ New_HTMLDefinition,
+ roles::DEFINITION)
+
+MARKUPMAP(details,
+ New_HyperText,
+ roles::DETAILS)
+
+MARKUPMAP(div,
+ nullptr,
+ roles::SECTION)
+
+MARKUPMAP(dl,
+ New_HTMLList,
+ roles::DEFINITION_LIST)
+
+MARKUPMAP(dt,
+ New_HTMLListitem,
+ roles::TERM)
+
+MARKUPMAP(figcaption,
+ New_HTMLFigcaption,
+ roles::CAPTION)
+
+MARKUPMAP(figure,
+ New_HTMLFigure,
+ roles::FIGURE,
+ Attr(xmlroles, figure))
+
+MARKUPMAP(form,
+ New_HyperText,
+ roles::FORM)
+
+MARKUPMAP(footer,
+ New_HyperText,
+ roles::FOOTER)
+
+MARKUPMAP(header,
+ New_HyperText,
+ roles::HEADER)
+
+MARKUPMAP(h1,
+ New_HyperText,
+ roles::HEADING)
+
+MARKUPMAP(h2,
+ New_HyperText,
+ roles::HEADING)
+
+MARKUPMAP(h3,
+ New_HyperText,
+ roles::HEADING)
+
+MARKUPMAP(h4,
+ New_HyperText,
+ roles::HEADING)
+
+MARKUPMAP(h5,
+ New_HyperText,
+ roles::HEADING)
+
+MARKUPMAP(h6,
+ New_HyperText,
+ roles::HEADING)
+
+MARKUPMAP(label,
+ New_HTMLLabel,
+ roles::LABEL)
+
+MARKUPMAP(legend,
+ New_HTMLLegend,
+ roles::LABEL)
+
+MARKUPMAP(li,
+ New_HTMLListitem,
+ 0)
+
+MARKUPMAP(map,
+ nullptr,
+ roles::TEXT_CONTAINER)
+
+MARKUPMAP(math,
+ New_HyperText,
+ roles::MATHML_MATH)
+
+MARKUPMAP(mi_,
+ New_HyperText,
+ roles::MATHML_IDENTIFIER)
+
+MARKUPMAP(mn_,
+ New_HyperText,
+ roles::MATHML_NUMBER)
+
+MARKUPMAP(mo_,
+ New_HyperText,
+ roles::MATHML_OPERATOR,
+ AttrFromDOM(accent_, accent_),
+ AttrFromDOM(fence_, fence_),
+ AttrFromDOM(separator_, separator_),
+ AttrFromDOM(largeop_, largeop_))
+
+MARKUPMAP(mtext_,
+ New_HyperText,
+ roles::MATHML_TEXT)
+
+MARKUPMAP(ms_,
+ New_HyperText,
+ roles::MATHML_STRING_LITERAL)
+
+MARKUPMAP(mglyph_,
+ New_HyperText,
+ roles::MATHML_GLYPH)
+
+MARKUPMAP(mrow_,
+ New_HyperText,
+ roles::MATHML_ROW)
+
+MARKUPMAP(mfrac_,
+ New_HyperText,
+ roles::MATHML_FRACTION,
+ AttrFromDOM(bevelled_, bevelled_),
+ AttrFromDOM(linethickness_, linethickness_))
+
+MARKUPMAP(msqrt_,
+ New_HyperText,
+ roles::MATHML_SQUARE_ROOT)
+
+MARKUPMAP(mroot_,
+ New_HyperText,
+ roles::MATHML_ROOT)
+
+MARKUPMAP(mfenced_,
+ New_HyperText,
+ roles::MATHML_FENCED,
+ AttrFromDOM(close, close),
+ AttrFromDOM(open, open),
+ AttrFromDOM(separators_, separators_))
+
+MARKUPMAP(menclose_,
+ New_HyperText,
+ roles::MATHML_ENCLOSED,
+ AttrFromDOM(notation_, notation_))
+
+MARKUPMAP(mstyle_,
+ New_HyperText,
+ roles::MATHML_STYLE)
+
+MARKUPMAP(msub_,
+ New_HyperText,
+ roles::MATHML_SUB)
+
+MARKUPMAP(msup_,
+ New_HyperText,
+ roles::MATHML_SUP)
+
+MARKUPMAP(msubsup_,
+ New_HyperText,
+ roles::MATHML_SUB_SUP)
+
+MARKUPMAP(munder_,
+ New_HyperText,
+ roles::MATHML_UNDER,
+ AttrFromDOM(accentunder_, accentunder_),
+ AttrFromDOM(align, align))
+
+MARKUPMAP(mover_,
+ New_HyperText,
+ roles::MATHML_OVER,
+ AttrFromDOM(accent_, accent_),
+ AttrFromDOM(align, align))
+
+MARKUPMAP(munderover_,
+ New_HyperText,
+ roles::MATHML_UNDER_OVER,
+ AttrFromDOM(accent_, accent_),
+ AttrFromDOM(accentunder_, accentunder_),
+ AttrFromDOM(align, align))
+
+MARKUPMAP(mmultiscripts_,
+ New_HyperText,
+ roles::MATHML_MULTISCRIPTS)
+
+MARKUPMAP(mtable_,
+ New_HTMLTableAccessible,
+ roles::MATHML_TABLE,
+ AttrFromDOM(align, align),
+ AttrFromDOM(columnlines_, columnlines_),
+ AttrFromDOM(rowlines_, rowlines_))
+
+MARKUPMAP(mlabeledtr_,
+ New_HTMLTableRowAccessible,
+ roles::MATHML_LABELED_ROW)
+
+MARKUPMAP(mtr_,
+ New_HTMLTableRowAccessible,
+ roles::MATHML_TABLE_ROW)
+
+MARKUPMAP(mtd_,
+ New_HTMLTableCellAccessible,
+ roles::MATHML_CELL)
+
+MARKUPMAP(maction_,
+ New_HyperText,
+ roles::MATHML_ACTION,
+ AttrFromDOM(actiontype_, actiontype_),
+ AttrFromDOM(selection_, selection_))
+
+MARKUPMAP(merror_,
+ New_HyperText,
+ roles::MATHML_ERROR)
+
+MARKUPMAP(mstack_,
+ New_HyperText,
+ roles::MATHML_STACK,
+ AttrFromDOM(align, align),
+ AttrFromDOM(position, position))
+
+MARKUPMAP(mlongdiv_,
+ New_HyperText,
+ roles::MATHML_LONG_DIVISION,
+ AttrFromDOM(longdivstyle_, longdivstyle_))
+
+MARKUPMAP(msgroup_,
+ New_HyperText,
+ roles::MATHML_STACK_GROUP,
+ AttrFromDOM(position, position),
+ AttrFromDOM(shift_, shift_))
+
+MARKUPMAP(msrow_,
+ New_HyperText,
+ roles::MATHML_STACK_ROW,
+ AttrFromDOM(position, position))
+
+MARKUPMAP(mscarries_,
+ New_HyperText,
+ roles::MATHML_STACK_CARRIES,
+ AttrFromDOM(location_, location_),
+ AttrFromDOM(position, position))
+
+MARKUPMAP(mscarry_,
+ New_HyperText,
+ roles::MATHML_STACK_CARRY,
+ AttrFromDOM(crossout_, crossout_))
+
+MARKUPMAP(msline_,
+ New_HyperText,
+ roles::MATHML_STACK_LINE,
+ AttrFromDOM(position, position))
+
+MARKUPMAP(nav,
+ New_HyperText,
+ roles::SECTION)
+
+MARKUPMAP(ol,
+ New_HTMLList,
+ roles::LIST)
+
+MARKUPMAP(option,
+ New_HTMLOption,
+ 0)
+
+MARKUPMAP(optgroup,
+ New_HTMLOptgroup,
+ 0)
+
+MARKUPMAP(output,
+ New_HTMLOutput,
+ roles::SECTION,
+ Attr(live, polite))
+
+MARKUPMAP(p,
+ nullptr,
+ roles::PARAGRAPH)
+
+MARKUPMAP(progress,
+ New_HTMLProgress,
+ 0)
+
+MARKUPMAP(q,
+ New_HyperText,
+ 0)
+
+MARKUPMAP(section,
+ New_HyperText,
+ roles::SECTION,
+ Attr(xmlroles, region))
+
+MARKUPMAP(summary,
+ New_HTMLSummary,
+ roles::SUMMARY)
+
+MARKUPMAP(time,
+ New_HyperText,
+ 0,
+ Attr(xmlroles, time),
+ AttrFromDOM(datetime, datetime))
+
+MARKUPMAP(td,
+ New_HTMLTableHeaderCellIfScope,
+ 0)
+
+MARKUPMAP(th,
+ New_HTMLTableHeaderCell,
+ 0)
+
+MARKUPMAP(ul,
+ New_HTMLList,
+ roles::LIST)
diff --git a/accessible/base/NotificationController.cpp b/accessible/base/NotificationController.cpp
new file mode 100644
index 000000000..73d364641
--- /dev/null
+++ b/accessible/base/NotificationController.cpp
@@ -0,0 +1,947 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "NotificationController.h"
+
+#include "DocAccessible-inl.h"
+#include "DocAccessibleChild.h"
+#include "TextLeafAccessible.h"
+#include "TextUpdater.h"
+
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/Telemetry.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// NotificationCollector
+////////////////////////////////////////////////////////////////////////////////
+
+NotificationController::NotificationController(DocAccessible* aDocument,
+ nsIPresShell* aPresShell) :
+ EventQueue(aDocument), mObservingState(eNotObservingRefresh),
+ mPresShell(aPresShell), mEventGeneration(0)
+{
+#ifdef DEBUG
+ mMoveGuardOnStack = false;
+#endif
+
+ // Schedule initial accessible tree construction.
+ ScheduleProcessing();
+}
+
+NotificationController::~NotificationController()
+{
+ NS_ASSERTION(!mDocument, "Controller wasn't shutdown properly!");
+ if (mDocument)
+ Shutdown();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NotificationCollector: AddRef/Release and cycle collection
+
+NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(NotificationController)
+NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(NotificationController)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(NotificationController)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(NotificationController)
+ if (tmp->mDocument)
+ tmp->Shutdown();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(NotificationController)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHangingChildDocuments)
+ for (auto it = tmp->mContentInsertions.ConstIter(); !it.Done(); it.Next()) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mContentInsertions key");
+ cb.NoteXPCOMChild(it.Key());
+ nsTArray<nsCOMPtr<nsIContent>>* list = it.UserData();
+ for (uint32_t i = 0; i < list->Length(); i++) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
+ "mContentInsertions value item");
+ cb.NoteXPCOMChild(list->ElementAt(i));
+ }
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvents)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelocations)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(NotificationController, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(NotificationController, Release)
+
+////////////////////////////////////////////////////////////////////////////////
+// NotificationCollector: public
+
+void
+NotificationController::Shutdown()
+{
+ if (mObservingState != eNotObservingRefresh &&
+ mPresShell->RemoveRefreshObserver(this, Flush_Display)) {
+ mObservingState = eNotObservingRefresh;
+ }
+
+ // Shutdown handling child documents.
+ int32_t childDocCount = mHangingChildDocuments.Length();
+ for (int32_t idx = childDocCount - 1; idx >= 0; idx--) {
+ if (!mHangingChildDocuments[idx]->IsDefunct())
+ mHangingChildDocuments[idx]->Shutdown();
+ }
+
+ mHangingChildDocuments.Clear();
+
+ mDocument = nullptr;
+ mPresShell = nullptr;
+
+ mTextHash.Clear();
+ mContentInsertions.Clear();
+ mNotifications.Clear();
+ mEvents.Clear();
+ mRelocations.Clear();
+ mEventTree.Clear();
+}
+
+EventTree*
+NotificationController::QueueMutation(Accessible* aContainer)
+{
+ EventTree* tree = mEventTree.FindOrInsert(aContainer);
+ if (tree) {
+ ScheduleProcessing();
+ }
+ return tree;
+}
+
+bool
+NotificationController::QueueMutationEvent(AccTreeMutationEvent* aEvent)
+{
+ // We have to allow there to be a hide and then a show event for a target
+ // because of targets getting moved. However we need to coalesce a show and
+ // then a hide for a target which means we need to check for that here.
+ if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE &&
+ aEvent->GetAccessible()->ShowEventTarget()) {
+ AccTreeMutationEvent* showEvent = mMutationMap.GetEvent(aEvent->GetAccessible(), EventMap::ShowEvent);
+ DropMutationEvent(showEvent);
+ return false;
+ }
+
+ AccMutationEvent* mutEvent = downcast_accEvent(aEvent);
+ mEventGeneration++;
+ mutEvent->SetEventGeneration(mEventGeneration);
+
+ if (!mFirstMutationEvent) {
+ mFirstMutationEvent = aEvent;
+ ScheduleProcessing();
+ }
+
+ if (mLastMutationEvent) {
+ NS_ASSERTION(!mLastMutationEvent->NextEvent(), "why isn't the last event the end?");
+ mLastMutationEvent->SetNextEvent(aEvent);
+ }
+
+ aEvent->SetPrevEvent(mLastMutationEvent);
+ mLastMutationEvent = aEvent;
+ mMutationMap.PutEvent(aEvent);
+
+ // Because we could be hiding the target of a show event we need to get rid
+ // of any such events. It may be possible to do less than coallesce all
+ // events, however that is easiest.
+ if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE) {
+ CoalesceMutationEvents();
+
+ // mLastMutationEvent will point to something other than aEvent if and only
+ // if aEvent was just coalesced away. In that case a parent accessible
+ // must already have the required reorder and text change events so we are
+ // done here.
+ if (mLastMutationEvent != aEvent) {
+ return false;
+ }
+ }
+
+ // We need to fire a reorder event after all of the events targeted at shown or
+ // hidden children of a container. So either queue a new one, or move an
+ // existing one to the end of the queue if the container already has a
+ // reorder event.
+ Accessible* target = aEvent->GetAccessible();
+ Accessible* container = aEvent->GetAccessible()->Parent();
+ RefPtr<AccReorderEvent> reorder;
+ if (!container->ReorderEventTarget()) {
+ reorder = new AccReorderEvent(container);
+ container->SetReorderEventTarget(true);
+ mMutationMap.PutEvent(reorder);
+
+ // Since this is the first child of container that is changing, the name of
+ // container may be changing.
+ QueueNameChange(target);
+ } else {
+ AccReorderEvent* event = downcast_accEvent(mMutationMap.GetEvent(container, EventMap::ReorderEvent));
+ reorder = event;
+ if (mFirstMutationEvent == event) {
+ mFirstMutationEvent = event->NextEvent();
+ } else {
+ event->PrevEvent()->SetNextEvent(event->NextEvent());
+ }
+
+ event->NextEvent()->SetPrevEvent(event->PrevEvent());
+ event->SetNextEvent(nullptr);
+ }
+
+ reorder->SetEventGeneration(mEventGeneration);
+ reorder->SetPrevEvent(mLastMutationEvent);
+ mLastMutationEvent->SetNextEvent(reorder);
+ mLastMutationEvent = reorder;
+
+ // It is not possible to have a text change event for something other than a
+ // hyper text accessible.
+ if (!container->IsHyperText()) {
+ return true;
+ }
+
+ MOZ_ASSERT(mutEvent);
+
+ nsString text;
+ aEvent->GetAccessible()->AppendTextTo(text);
+ if (text.IsEmpty()) {
+ return true;
+ }
+
+ int32_t offset = container->AsHyperText()->GetChildOffset(target);
+ AccTreeMutationEvent* prevEvent = aEvent->PrevEvent();
+ while (prevEvent && prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) {
+ prevEvent = prevEvent->PrevEvent();
+ }
+
+ if (prevEvent && prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE &&
+ mutEvent->IsHide()) {
+ AccHideEvent* prevHide = downcast_accEvent(prevEvent);
+ AccTextChangeEvent* prevTextChange = prevHide->mTextChangeEvent;
+ if (prevTextChange && prevHide->Parent() == mutEvent->Parent()) {
+ if (prevHide->mNextSibling == target) {
+ target->AppendTextTo(prevTextChange->mModifiedText);
+ prevHide->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
+ } else if (prevHide->mPrevSibling == target) {
+ nsString temp;
+ target->AppendTextTo(temp);
+
+ uint32_t extraLen = temp.Length();
+ temp += prevTextChange->mModifiedText;;
+ prevTextChange->mModifiedText = temp;
+ prevTextChange->mStart -= extraLen;
+ prevHide->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
+ }
+ }
+ } else if (prevEvent && mutEvent->IsShow() &&
+ prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) {
+ AccShowEvent* prevShow = downcast_accEvent(prevEvent);
+ AccTextChangeEvent* prevTextChange = prevShow->mTextChangeEvent;
+ if (prevTextChange && prevShow->Parent() == target->Parent()) {
+ int32_t index = target->IndexInParent();
+ int32_t prevIndex = prevShow->GetAccessible()->IndexInParent();
+ if (prevIndex + 1 == index) {
+ target->AppendTextTo(prevTextChange->mModifiedText);
+ prevShow->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
+ } else if (index + 1 == prevIndex) {
+ nsString temp;
+ target->AppendTextTo(temp);
+ prevTextChange->mStart -= temp.Length();
+ temp += prevTextChange->mModifiedText;
+ prevTextChange->mModifiedText = temp;
+ prevShow->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
+ }
+ }
+ }
+
+ if (!mutEvent->mTextChangeEvent) {
+ mutEvent->mTextChangeEvent =
+ new AccTextChangeEvent(container, offset, text, mutEvent->IsShow(),
+ aEvent->mIsFromUserInput ? eFromUserInput : eNoUserInput);
+ }
+
+ return true;
+}
+
+void
+NotificationController::DropMutationEvent(AccTreeMutationEvent* aEvent)
+{
+ // unset the event bits since the event isn't being fired any more.
+ if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) {
+ aEvent->GetAccessible()->SetReorderEventTarget(false);
+ } else if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) {
+ aEvent->GetAccessible()->SetShowEventTarget(false);
+ } else {
+ AccHideEvent* hideEvent = downcast_accEvent(aEvent);
+ MOZ_ASSERT(hideEvent);
+
+ if (hideEvent->NeedsShutdown()) {
+ mDocument->ShutdownChildrenInSubtree(aEvent->GetAccessible());
+ }
+ }
+
+ // Do the work to splice the event out of the list.
+ if (mFirstMutationEvent == aEvent) {
+ mFirstMutationEvent = aEvent->NextEvent();
+ } else {
+ aEvent->PrevEvent()->SetNextEvent(aEvent->NextEvent());
+ }
+
+ if (mLastMutationEvent == aEvent) {
+ mLastMutationEvent = aEvent->PrevEvent();
+ } else {
+ aEvent->NextEvent()->SetPrevEvent(aEvent->PrevEvent());
+ }
+
+ aEvent->SetPrevEvent(nullptr);
+ aEvent->SetNextEvent(nullptr);
+ mMutationMap.RemoveEvent(aEvent);
+}
+
+void
+NotificationController::CoalesceMutationEvents()
+{
+ AccTreeMutationEvent* event = mFirstMutationEvent;
+ while (event) {
+ AccTreeMutationEvent* nextEvent = event->NextEvent();
+ uint32_t eventType = event->GetEventType();
+ if (event->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) {
+ Accessible* acc = event->GetAccessible();
+ while (acc) {
+ if (acc->IsDoc()) {
+ break;
+ }
+
+ // if a parent of the reorder event's target is being hidden that
+ // hide event's target must have a parent that is also a reorder event
+ // target. That means we don't need this reorder event.
+ if (acc->HideEventTarget()) {
+ DropMutationEvent(event);
+ break;
+ }
+
+ Accessible* parent = acc->Parent();
+ if (parent->ReorderEventTarget()) {
+ AccReorderEvent* reorder = downcast_accEvent(mMutationMap.GetEvent(parent, EventMap::ReorderEvent));
+
+ // We want to make sure that a reorder event comes after any show or
+ // hide events targeted at the children of its target. We keep the
+ // invariant that event generation goes up as you are farther in the
+ // queue, so we want to use the spot of the event with the higher
+ // generation number, and keep that generation number.
+ if (reorder && reorder->EventGeneration() < event->EventGeneration()) {
+ reorder->SetEventGeneration(event->EventGeneration());
+
+ // It may be true that reorder was before event, and we coalesced
+ // away all the show / hide events between them. In that case
+ // event is already immediately after reorder in the queue and we
+ // do not need to rearrange the list of events.
+ if (event != reorder->NextEvent()) {
+ // There really should be a show or hide event before the first
+ // reorder event.
+ if (reorder->PrevEvent()) {
+ reorder->PrevEvent()->SetNextEvent(reorder->NextEvent());
+ } else {
+ mFirstMutationEvent = reorder->NextEvent();
+ }
+
+ reorder->NextEvent()->SetPrevEvent(reorder->PrevEvent());
+ event->PrevEvent()->SetNextEvent(reorder);
+ reorder->SetPrevEvent(event->PrevEvent());
+ event->SetPrevEvent(reorder);
+ reorder->SetNextEvent(event);
+ }
+ }
+ DropMutationEvent(event);
+ break;
+ }
+
+ acc = parent;
+ }
+ } else if (eventType == nsIAccessibleEvent::EVENT_SHOW) {
+ Accessible* parent = event->GetAccessible()->Parent();
+ while (parent) {
+ if (parent->IsDoc()) {
+ break;
+ }
+
+ // if the parent of a show event is being either shown or hidden then
+ // we don't need to fire a show event for a subtree of that change.
+ if (parent->ShowEventTarget() || parent->HideEventTarget()) {
+ DropMutationEvent(event);
+ break;
+ }
+
+ parent = parent->Parent();
+ }
+ } else {
+ MOZ_ASSERT(eventType == nsIAccessibleEvent::EVENT_HIDE, "mutation event list has an invalid event");
+
+ AccHideEvent* hideEvent = downcast_accEvent(event);
+ Accessible* parent = hideEvent->Parent();
+ while (parent) {
+ if (parent->IsDoc()) {
+ break;
+ }
+
+ if (parent->HideEventTarget()) {
+ DropMutationEvent(event);
+ break;
+ }
+
+ if (parent->ShowEventTarget()) {
+ AccShowEvent* showEvent = downcast_accEvent(mMutationMap.GetEvent(parent, EventMap::ShowEvent));
+ if (showEvent->EventGeneration() < hideEvent->EventGeneration()) {
+ DropMutationEvent(hideEvent);
+ break;
+ }
+ }
+
+ parent = parent->Parent();
+ }
+ }
+
+ event = nextEvent;
+ }
+}
+
+void
+NotificationController::ScheduleChildDocBinding(DocAccessible* aDocument)
+{
+ // Schedule child document binding to the tree.
+ mHangingChildDocuments.AppendElement(aDocument);
+ ScheduleProcessing();
+}
+
+void
+NotificationController::ScheduleContentInsertion(Accessible* aContainer,
+ nsIContent* aStartChildNode,
+ nsIContent* aEndChildNode)
+{
+ nsTArray<nsCOMPtr<nsIContent>>* list =
+ mContentInsertions.LookupOrAdd(aContainer);
+
+ bool needsProcessing = false;
+ nsIContent* node = aStartChildNode;
+ while (node != aEndChildNode) {
+ // Notification triggers for content insertion even if no content was
+ // actually inserted, check if the given content has a frame to discard
+ // this case early.
+ if (node->GetPrimaryFrame()) {
+ if (list->AppendElement(node))
+ needsProcessing = true;
+ }
+ node = node->GetNextSibling();
+ }
+
+ if (needsProcessing) {
+ ScheduleProcessing();
+ }
+}
+
+void
+NotificationController::ScheduleProcessing()
+{
+ // If notification flush isn't planed yet start notification flush
+ // asynchronously (after style and layout).
+ if (mObservingState == eNotObservingRefresh) {
+ if (mPresShell->AddRefreshObserver(this, Flush_Display))
+ mObservingState = eRefreshObserving;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NotificationCollector: protected
+
+bool
+NotificationController::IsUpdatePending()
+{
+ return mPresShell->IsLayoutFlushObserver() ||
+ mObservingState == eRefreshProcessingForUpdate ||
+ mContentInsertions.Count() != 0 || mNotifications.Length() != 0 ||
+ mTextHash.Count() != 0 ||
+ !mDocument->HasLoadState(DocAccessible::eTreeConstructed);
+}
+
+void
+NotificationController::ProcessMutationEvents()
+{
+ // there is no reason to fire a hide event for a child of a show event
+ // target. That can happen if something is inserted into the tree and
+ // removed before the next refresh driver tick, but it should not be
+ // observable outside gecko so it should be safe to coalesce away any such
+ // events. This means that it should be fine to fire all of the hide events
+ // first, and then deal with any shown subtrees.
+ for (AccTreeMutationEvent* event = mFirstMutationEvent;
+ event; event = event->NextEvent()) {
+ if (event->GetEventType() != nsIAccessibleEvent::EVENT_HIDE) {
+ continue;
+ }
+
+ nsEventShell::FireEvent(event);
+ if (!mDocument) {
+ return;
+ }
+
+ AccMutationEvent* mutEvent = downcast_accEvent(event);
+ if (mutEvent->mTextChangeEvent) {
+ nsEventShell::FireEvent(mutEvent->mTextChangeEvent);
+ if (!mDocument) {
+ return;
+ }
+ }
+
+ // Fire menupopup end event before a hide event if a menu goes away.
+
+ // XXX: We don't look into children of hidden subtree to find hiding
+ // menupopup (as we did prior bug 570275) because we don't do that when
+ // menu is showing (and that's impossible until bug 606924 is fixed).
+ // Nevertheless we should do this at least because layout coalesces
+ // the changes before our processing and we may miss some menupopup
+ // events. Now we just want to be consistent in content insertion/removal
+ // handling.
+ if (event->mAccessible->ARIARole() == roles::MENUPOPUP) {
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END,
+ event->mAccessible);
+ if (!mDocument) {
+ return;
+ }
+ }
+
+ AccHideEvent* hideEvent = downcast_accEvent(event);
+ if (hideEvent->NeedsShutdown()) {
+ mDocument->ShutdownChildrenInSubtree(event->mAccessible);
+ }
+ }
+
+ // Group the show events by the parent of their target.
+ nsDataHashtable<nsPtrHashKey<Accessible>, nsTArray<AccTreeMutationEvent*>> showEvents;
+ for (AccTreeMutationEvent* event = mFirstMutationEvent;
+ event; event = event->NextEvent()) {
+ if (event->GetEventType() != nsIAccessibleEvent::EVENT_SHOW) {
+ continue;
+ }
+
+ Accessible* parent = event->GetAccessible()->Parent();
+ showEvents.GetOrInsert(parent).AppendElement(event);
+ }
+
+ // We need to fire show events for the children of an accessible in the order
+ // of their indices at this point. So sort each set of events for the same
+ // container by the index of their target.
+ for (auto iter = showEvents.Iter(); !iter.Done(); iter.Next()) {
+ struct AccIdxComparator {
+ bool LessThan(const AccTreeMutationEvent* a, const AccTreeMutationEvent* b) const
+ {
+ int32_t aIdx = a->GetAccessible()->IndexInParent();
+ int32_t bIdx = b->GetAccessible()->IndexInParent();
+ MOZ_ASSERT(aIdx >= 0 && bIdx >= 0 && aIdx != bIdx);
+ return aIdx < bIdx;
+ }
+ bool Equals(const AccTreeMutationEvent* a, const AccTreeMutationEvent* b) const
+ {
+ DebugOnly<int32_t> aIdx = a->GetAccessible()->IndexInParent();
+ DebugOnly<int32_t> bIdx = b->GetAccessible()->IndexInParent();
+ MOZ_ASSERT(aIdx >= 0 && bIdx >= 0 && aIdx != bIdx);
+ return false;
+ }
+ };
+
+ nsTArray<AccTreeMutationEvent*>& events = iter.Data();
+ events.Sort(AccIdxComparator());
+ for (AccTreeMutationEvent* event: events) {
+ nsEventShell::FireEvent(event);
+ if (!mDocument) {
+ return;
+ }
+
+ AccMutationEvent* mutEvent = downcast_accEvent(event);
+ if (mutEvent->mTextChangeEvent) {
+ nsEventShell::FireEvent(mutEvent->mTextChangeEvent);
+ if (!mDocument) {
+ return;
+ }
+ }
+ }
+ }
+
+ // Now we can fire the reorder events after all the show and hide events.
+ for (AccTreeMutationEvent* event = mFirstMutationEvent;
+ event; event = event->NextEvent()) {
+ if (event->GetEventType() != nsIAccessibleEvent::EVENT_REORDER) {
+ continue;
+ }
+
+ nsEventShell::FireEvent(event);
+ if (!mDocument) {
+ return;
+ }
+
+ Accessible* target = event->GetAccessible();
+ target->Document()->MaybeNotifyOfValueChange(target);
+ if (!mDocument) {
+ return;
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NotificationCollector: private
+
+void
+NotificationController::WillRefresh(mozilla::TimeStamp aTime)
+{
+ PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER);
+ Telemetry::AutoTimer<Telemetry::A11Y_UPDATE_TIME> updateTimer;
+
+ // If the document accessible that notification collector was created for is
+ // now shut down, don't process notifications anymore.
+ NS_ASSERTION(mDocument,
+ "The document was shut down while refresh observer is attached!");
+ if (!mDocument)
+ return;
+
+ if (mObservingState == eRefreshProcessing ||
+ mObservingState == eRefreshProcessingForUpdate)
+ return;
+
+ // Any generic notifications should be queued if we're processing content
+ // insertions or generic notifications.
+ mObservingState = eRefreshProcessingForUpdate;
+
+ // Initial accessible tree construction.
+ if (!mDocument->HasLoadState(DocAccessible::eTreeConstructed)) {
+ // If document is not bound to parent at this point then the document is not
+ // ready yet (process notifications later).
+ if (!mDocument->IsBoundToParent()) {
+ mObservingState = eRefreshObserving;
+ return;
+ }
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eTree)) {
+ logging::MsgBegin("TREE", "initial tree created");
+ logging::Address("document", mDocument);
+ logging::MsgEnd();
+ }
+#endif
+
+ mDocument->DoInitialUpdate();
+
+ NS_ASSERTION(mContentInsertions.Count() == 0,
+ "Pending content insertions while initial accessible tree isn't created!");
+ }
+
+ // Initialize scroll support if needed.
+ if (!(mDocument->mDocFlags & DocAccessible::eScrollInitialized))
+ mDocument->AddScrollListener();
+
+ // Process rendered text change notifications.
+ for (auto iter = mTextHash.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtrHashKey<nsIContent>* entry = iter.Get();
+ nsIContent* textNode = entry->GetKey();
+ Accessible* textAcc = mDocument->GetAccessible(textNode);
+
+ // If the text node is not in tree or doesn't have frame then this case should
+ // have been handled already by content removal notifications.
+ nsINode* containerNode = textNode->GetParentNode();
+ if (!containerNode) {
+ NS_ASSERTION(!textAcc,
+ "Text node was removed but accessible is kept alive!");
+ continue;
+ }
+
+ nsIFrame* textFrame = textNode->GetPrimaryFrame();
+ if (!textFrame) {
+ NS_ASSERTION(!textAcc,
+ "Text node isn't rendered but accessible is kept alive!");
+ continue;
+ }
+
+ nsIContent* containerElm = containerNode->IsElement() ?
+ containerNode->AsElement() : nullptr;
+
+ nsIFrame::RenderedText text = textFrame->GetRenderedText(0,
+ UINT32_MAX, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
+ nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
+
+ // Remove text accessible if rendered text is empty.
+ if (textAcc) {
+ if (text.mString.IsEmpty()) {
+ #ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eTree | logging::eText)) {
+ logging::MsgBegin("TREE", "text node lost its content; doc: %p", mDocument);
+ logging::Node("container", containerElm);
+ logging::Node("content", textNode);
+ logging::MsgEnd();
+ }
+ #endif
+
+ mDocument->ContentRemoved(containerElm, textNode);
+ continue;
+ }
+
+ // Update text of the accessible and fire text change events.
+ #ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eText)) {
+ logging::MsgBegin("TEXT", "text may be changed; doc: %p", mDocument);
+ logging::Node("container", containerElm);
+ logging::Node("content", textNode);
+ logging::MsgEntry("old text '%s'",
+ NS_ConvertUTF16toUTF8(textAcc->AsTextLeaf()->Text()).get());
+ logging::MsgEntry("new text: '%s'",
+ NS_ConvertUTF16toUTF8(text.mString).get());
+ logging::MsgEnd();
+ }
+ #endif
+
+ TextUpdater::Run(mDocument, textAcc->AsTextLeaf(), text.mString);
+ continue;
+ }
+
+ // Append an accessible if rendered text is not empty.
+ if (!text.mString.IsEmpty()) {
+ #ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eTree | logging::eText)) {
+ logging::MsgBegin("TREE", "text node gains new content; doc: %p", mDocument);
+ logging::Node("container", containerElm);
+ logging::Node("content", textNode);
+ logging::MsgEnd();
+ }
+ #endif
+
+ Accessible* container = mDocument->AccessibleOrTrueContainer(containerNode);
+ MOZ_ASSERT(container,
+ "Text node having rendered text hasn't accessible document!");
+ if (container) {
+ nsTArray<nsCOMPtr<nsIContent>>* list =
+ mContentInsertions.LookupOrAdd(container);
+ list->AppendElement(textNode);
+ }
+ }
+ }
+ mTextHash.Clear();
+
+ // Process content inserted notifications to update the tree.
+ for (auto iter = mContentInsertions.ConstIter(); !iter.Done(); iter.Next()) {
+ mDocument->ProcessContentInserted(iter.Key(), iter.UserData());
+ if (!mDocument) {
+ return;
+ }
+ }
+ mContentInsertions.Clear();
+
+ // Bind hanging child documents.
+ uint32_t hangingDocCnt = mHangingChildDocuments.Length();
+ nsTArray<RefPtr<DocAccessible>> newChildDocs;
+ for (uint32_t idx = 0; idx < hangingDocCnt; idx++) {
+ DocAccessible* childDoc = mHangingChildDocuments[idx];
+ if (childDoc->IsDefunct())
+ continue;
+
+ nsIContent* ownerContent = mDocument->DocumentNode()->
+ FindContentForSubDocument(childDoc->DocumentNode());
+ if (ownerContent) {
+ Accessible* outerDocAcc = mDocument->GetAccessible(ownerContent);
+ if (outerDocAcc && outerDocAcc->AppendChild(childDoc)) {
+ if (mDocument->AppendChildDocument(childDoc)) {
+ newChildDocs.AppendElement(Move(mHangingChildDocuments[idx]));
+ continue;
+ }
+
+ outerDocAcc->RemoveChild(childDoc);
+ }
+
+ // Failed to bind the child document, destroy it.
+ childDoc->Shutdown();
+ }
+ }
+
+ mHangingChildDocuments.Clear();
+
+ // If the document is ready and all its subdocuments are completely loaded
+ // then process the document load.
+ if (mDocument->HasLoadState(DocAccessible::eReady) &&
+ !mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) &&
+ hangingDocCnt == 0) {
+ uint32_t childDocCnt = mDocument->ChildDocumentCount(), childDocIdx = 0;
+ for (; childDocIdx < childDocCnt; childDocIdx++) {
+ DocAccessible* childDoc = mDocument->GetChildDocumentAt(childDocIdx);
+ if (!childDoc->HasLoadState(DocAccessible::eCompletelyLoaded))
+ break;
+ }
+
+ if (childDocIdx == childDocCnt) {
+ mDocument->ProcessLoad();
+ if (!mDocument)
+ return;
+ }
+ }
+
+ // Process only currently queued generic notifications.
+ nsTArray < RefPtr<Notification> > notifications;
+ notifications.SwapElements(mNotifications);
+
+ uint32_t notificationCount = notifications.Length();
+ for (uint32_t idx = 0; idx < notificationCount; idx++) {
+ notifications[idx]->Process();
+ if (!mDocument)
+ return;
+ }
+
+ // Process invalidation list of the document after all accessible tree
+ // modification are done.
+ mDocument->ProcessInvalidationList();
+
+ // We cannot rely on DOM tree to keep aria-owns relations updated. Make
+ // a validation to remove dead links.
+ mDocument->ValidateARIAOwned();
+
+ // Process relocation list.
+ for (uint32_t idx = 0; idx < mRelocations.Length(); idx++) {
+ if (mRelocations[idx]->IsInDocument()) {
+ mDocument->DoARIAOwnsRelocation(mRelocations[idx]);
+ }
+ }
+ mRelocations.Clear();
+
+ // If a generic notification occurs after this point then we may be allowed to
+ // process it synchronously. However we do not want to reenter if fireing
+ // events causes script to run.
+ mObservingState = eRefreshProcessing;
+
+ CoalesceMutationEvents();
+ ProcessMutationEvents();
+ mEventGeneration = 0;
+
+ // Now that we are done with them get rid of the events we fired.
+ RefPtr<AccTreeMutationEvent> mutEvent = Move(mFirstMutationEvent);
+ mLastMutationEvent = nullptr;
+ mFirstMutationEvent = nullptr;
+ while (mutEvent) {
+ RefPtr<AccTreeMutationEvent> nextEvent = mutEvent->NextEvent();
+ Accessible* target = mutEvent->GetAccessible();
+
+ // We need to be careful here, while it may seem that we can simply 0 all
+ // the pending event bits that is not true. Because accessibles may be
+ // reparented they may be the target of both a hide event and a show event
+ // at the same time.
+ if (mutEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) {
+ target->SetShowEventTarget(false);
+ }
+
+ if (mutEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE) {
+ target->SetHideEventTarget(false);
+ }
+
+ // However it is not possible for a reorder event target to also be the
+ // target of a show or hide, so we can just zero that.
+ target->SetReorderEventTarget(false);
+
+ mutEvent->SetPrevEvent(nullptr);
+ mutEvent->SetNextEvent(nullptr);
+ mMutationMap.RemoveEvent(mutEvent);
+ mutEvent = nextEvent;
+ }
+
+ ProcessEventQueue();
+
+ if (IPCAccessibilityActive()) {
+ size_t newDocCount = newChildDocs.Length();
+ for (size_t i = 0; i < newDocCount; i++) {
+ DocAccessible* childDoc = newChildDocs[i];
+ if (childDoc->IsDefunct()) {
+ continue;
+ }
+
+ Accessible* parent = childDoc->Parent();
+ DocAccessibleChild* parentIPCDoc = mDocument->IPCDoc();
+ uint64_t id = reinterpret_cast<uintptr_t>(parent->UniqueID());
+ MOZ_ASSERT(id);
+ DocAccessibleChild* ipcDoc = childDoc->IPCDoc();
+ if (ipcDoc) {
+ parentIPCDoc->SendBindChildDoc(ipcDoc, id);
+ continue;
+ }
+
+ ipcDoc = new DocAccessibleChild(childDoc);
+ childDoc->SetIPCDoc(ipcDoc);
+
+#if defined(XP_WIN)
+ MOZ_ASSERT(parentIPCDoc);
+ parentIPCDoc->ConstructChildDocInParentProcess(ipcDoc, id,
+ AccessibleWrap::GetChildIDFor(childDoc));
+#else
+ nsCOMPtr<nsITabChild> tabChild =
+ do_GetInterface(mDocument->DocumentNode()->GetDocShell());
+ if (tabChild) {
+ MOZ_ASSERT(parentIPCDoc);
+ static_cast<TabChild*>(tabChild.get())->
+ SendPDocAccessibleConstructor(ipcDoc, parentIPCDoc, id, 0, 0);
+ }
+#endif
+ }
+ }
+
+ mObservingState = eRefreshObserving;
+ if (!mDocument)
+ return;
+
+ // Stop further processing if there are no new notifications of any kind or
+ // events and document load is processed.
+ if (mContentInsertions.Count() == 0 && mNotifications.IsEmpty() &&
+ mEvents.IsEmpty() && mTextHash.Count() == 0 &&
+ mHangingChildDocuments.IsEmpty() &&
+ mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) &&
+ mPresShell->RemoveRefreshObserver(this, Flush_Display)) {
+ mObservingState = eNotObservingRefresh;
+ }
+}
+
+void
+NotificationController::EventMap::PutEvent(AccTreeMutationEvent* aEvent)
+{
+ EventType type = GetEventType(aEvent);
+ uint64_t addr = reinterpret_cast<uintptr_t>(aEvent->GetAccessible());
+ MOZ_ASSERT((addr & 0x3) == 0, "accessible is not 4 byte aligned");
+ addr |= type;
+ mTable.Put(addr, aEvent);
+}
+
+AccTreeMutationEvent*
+NotificationController::EventMap::GetEvent(Accessible* aTarget, EventType aType)
+{
+ uint64_t addr = reinterpret_cast<uintptr_t>(aTarget);
+ MOZ_ASSERT((addr & 0x3) == 0, "target is not 4 byte aligned");
+
+ addr |= aType;
+ return mTable.GetWeak(addr);
+}
+
+void
+NotificationController::EventMap::RemoveEvent(AccTreeMutationEvent* aEvent)
+{
+ EventType type = GetEventType(aEvent);
+ uint64_t addr = reinterpret_cast<uintptr_t>(aEvent->GetAccessible());
+ MOZ_ASSERT((addr & 0x3) == 0, "accessible is not 4 byte aligned");
+ addr |= type;
+
+ MOZ_ASSERT(mTable.GetWeak(addr) == aEvent, "mTable has the wrong event");
+ mTable.Remove(addr);
+}
+
+ NotificationController::EventMap::EventType
+NotificationController::EventMap::GetEventType(AccTreeMutationEvent* aEvent)
+{
+ switch(aEvent->GetEventType())
+ {
+ case nsIAccessibleEvent::EVENT_SHOW:
+ return ShowEvent;
+ case nsIAccessibleEvent::EVENT_HIDE:
+ return HideEvent;
+ case nsIAccessibleEvent::EVENT_REORDER:
+ return ReorderEvent;
+ default:
+ MOZ_ASSERT_UNREACHABLE("event has invalid type");
+ return ShowEvent;
+ }
+}
diff --git a/accessible/base/NotificationController.h b/accessible/base/NotificationController.h
new file mode 100644
index 000000000..a909fc63d
--- /dev/null
+++ b/accessible/base/NotificationController.h
@@ -0,0 +1,439 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_NotificationController_h_
+#define mozilla_a11y_NotificationController_h_
+
+#include "EventQueue.h"
+#include "EventTree.h"
+
+#include "mozilla/IndexSequence.h"
+#include "mozilla/Tuple.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsRefreshDriver.h"
+
+#ifdef A11Y_LOG
+#include "Logging.h"
+#endif
+
+namespace mozilla {
+namespace a11y {
+
+class DocAccessible;
+
+/**
+ * Notification interface.
+ */
+class Notification
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(mozilla::a11y::Notification)
+
+ /**
+ * Process notification.
+ */
+ virtual void Process() = 0;
+
+protected:
+ Notification() { }
+
+ /**
+ * Protected destructor, to discourage deletion outside of Release():
+ */
+ virtual ~Notification() { }
+
+private:
+ Notification(const Notification&);
+ Notification& operator = (const Notification&);
+};
+
+
+/**
+ * Template class for generic notification.
+ *
+ * @note Instance is kept as a weak ref, the caller must guarantee it exists
+ * longer than the document accessible owning the notification controller
+ * that this notification is processed by.
+ */
+template<class Class, class ... Args>
+class TNotification : public Notification
+{
+public:
+ typedef void (Class::*Callback)(Args* ...);
+
+ TNotification(Class* aInstance, Callback aCallback, Args* ... aArgs) :
+ mInstance(aInstance), mCallback(aCallback), mArgs(aArgs...) { }
+ virtual ~TNotification() { mInstance = nullptr; }
+
+ virtual void Process() override
+ { ProcessHelper(typename IndexSequenceFor<Args...>::Type()); }
+
+private:
+ TNotification(const TNotification&);
+ TNotification& operator = (const TNotification&);
+
+ template <size_t... Indices>
+ void ProcessHelper(IndexSequence<Indices...>)
+ {
+ (mInstance->*mCallback)(Get<Indices>(mArgs)...);
+ }
+
+ Class* mInstance;
+ Callback mCallback;
+ Tuple<RefPtr<Args> ...> mArgs;
+};
+
+/**
+ * Used to process notifications from core for the document accessible.
+ */
+class NotificationController final : public EventQueue,
+ public nsARefreshObserver
+{
+public:
+ NotificationController(DocAccessible* aDocument, nsIPresShell* aPresShell);
+
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override;
+ NS_IMETHOD_(MozExternalRefCountType) Release(void) override;
+
+ NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(NotificationController)
+
+ /**
+ * Shutdown the notification controller.
+ */
+ void Shutdown();
+
+ /**
+ * Add an accessible event into the queue to process it later.
+ */
+ void QueueEvent(AccEvent* aEvent)
+ {
+ if (PushEvent(aEvent)) {
+ ScheduleProcessing();
+ }
+ }
+
+ /**
+ * Creates and adds a name change event into the queue for a container of
+ * the given accessible, if the accessible is a part of name computation of
+ * the container.
+ */
+ void QueueNameChange(Accessible* aChangeTarget)
+ {
+ if (PushNameChange(aChangeTarget)) {
+ ScheduleProcessing();
+ }
+ }
+
+ /**
+ * Returns existing event tree for the given the accessible or creates one if
+ * it doesn't exists yet.
+ */
+ EventTree* QueueMutation(Accessible* aContainer);
+
+ class MoveGuard final {
+ public:
+ explicit MoveGuard(NotificationController* aController) :
+ mController(aController)
+ {
+#ifdef DEBUG
+ MOZ_ASSERT(!mController->mMoveGuardOnStack,
+ "Move guard is on stack already!");
+ mController->mMoveGuardOnStack = true;
+#endif
+ }
+ ~MoveGuard() {
+#ifdef DEBUG
+ MOZ_ASSERT(mController->mMoveGuardOnStack, "No move guard on stack!");
+ mController->mMoveGuardOnStack = false;
+#endif
+ mController->mPrecedingEvents.Clear();
+ }
+
+ private:
+ NotificationController* mController;
+ };
+
+#ifdef A11Y_LOG
+ const EventTree& RootEventTree() const { return mEventTree; };
+#endif
+
+ /**
+ * Queue a mutation event to emit if not coalesced away. Returns true if the
+ * event was queued and has not yet been coalesced.
+ */
+ bool QueueMutationEvent(AccTreeMutationEvent* aEvent);
+
+ /**
+ * Coalesce all queued mutation events.
+ */
+ void CoalesceMutationEvents();
+
+ /**
+ * Schedule binding the child document to the tree of this document.
+ */
+ void ScheduleChildDocBinding(DocAccessible* aDocument);
+
+ /**
+ * Schedule the accessible tree update because of rendered text changes.
+ */
+ inline void ScheduleTextUpdate(nsIContent* aTextNode)
+ {
+ // Make sure we are not called with a node that is not in the DOM tree or
+ // not visible.
+ MOZ_ASSERT(aTextNode->GetParentNode(), "A text node is not in DOM");
+ MOZ_ASSERT(aTextNode->GetPrimaryFrame(), "A text node doesn't have a frame");
+ MOZ_ASSERT(aTextNode->GetPrimaryFrame()->StyleVisibility()->IsVisible(),
+ "A text node is not visible");
+
+ mTextHash.PutEntry(aTextNode);
+ ScheduleProcessing();
+ }
+
+ /**
+ * Pend accessible tree update for content insertion.
+ */
+ void ScheduleContentInsertion(Accessible* aContainer,
+ nsIContent* aStartChildNode,
+ nsIContent* aEndChildNode);
+
+ /**
+ * Pend an accessible subtree relocation.
+ */
+ void ScheduleRelocation(Accessible* aOwner)
+ {
+ if (!mRelocations.Contains(aOwner) && mRelocations.AppendElement(aOwner)) {
+ ScheduleProcessing();
+ }
+ }
+
+ /**
+ * Start to observe refresh to make notifications and events processing after
+ * layout.
+ */
+ void ScheduleProcessing();
+
+ /**
+ * Process the generic notification synchronously if there are no pending
+ * layout changes and no notifications are pending or being processed right
+ * now. Otherwise, queue it up to process asynchronously.
+ *
+ * @note The caller must guarantee that the given instance still exists when
+ * the notification is processed.
+ */
+ template<class Class, class Arg>
+ inline void HandleNotification(Class* aInstance,
+ typename TNotification<Class, Arg>::Callback aMethod,
+ Arg* aArg)
+ {
+ if (!IsUpdatePending()) {
+#ifdef A11Y_LOG
+ if (mozilla::a11y::logging::IsEnabled(mozilla::a11y::logging::eNotifications))
+ mozilla::a11y::logging::Text("sync notification processing");
+#endif
+ (aInstance->*aMethod)(aArg);
+ return;
+ }
+
+ RefPtr<Notification> notification =
+ new TNotification<Class, Arg>(aInstance, aMethod, aArg);
+ if (notification && mNotifications.AppendElement(notification))
+ ScheduleProcessing();
+ }
+
+ /**
+ * Schedule the generic notification to process asynchronously.
+ *
+ * @note The caller must guarantee that the given instance still exists when
+ * the notification is processed.
+ */
+ template<class Class>
+ inline void ScheduleNotification(Class* aInstance,
+ typename TNotification<Class>::Callback aMethod)
+ {
+ RefPtr<Notification> notification =
+ new TNotification<Class>(aInstance, aMethod);
+ if (notification && mNotifications.AppendElement(notification))
+ ScheduleProcessing();
+ }
+
+#ifdef DEBUG
+ bool IsUpdating() const
+ { return mObservingState == eRefreshProcessingForUpdate; }
+#endif
+
+protected:
+ virtual ~NotificationController();
+
+ nsCycleCollectingAutoRefCnt mRefCnt;
+ NS_DECL_OWNINGTHREAD
+
+ /**
+ * Return true if the accessible tree state update is pending.
+ */
+ bool IsUpdatePending();
+
+private:
+ NotificationController(const NotificationController&);
+ NotificationController& operator = (const NotificationController&);
+
+ // nsARefreshObserver
+ virtual void WillRefresh(mozilla::TimeStamp aTime) override;
+
+ /**
+ * Set and returns a hide event, paired with a show event, for the move.
+ */
+ void WithdrawPrecedingEvents(nsTArray<RefPtr<AccHideEvent>>* aEvs)
+ {
+ if (mPrecedingEvents.Length() > 0) {
+ aEvs->AppendElements(mozilla::Move(mPrecedingEvents));
+ }
+ }
+ void StorePrecedingEvent(AccHideEvent* aEv)
+ {
+ MOZ_ASSERT(mMoveGuardOnStack, "No move guard on stack!");
+ mPrecedingEvents.AppendElement(aEv);
+ }
+ void StorePrecedingEvents(nsTArray<RefPtr<AccHideEvent>>&& aEvs)
+ {
+ MOZ_ASSERT(mMoveGuardOnStack, "No move guard on stack!");
+ mPrecedingEvents.InsertElementsAt(0, aEvs);
+ }
+
+private:
+ /**
+ * get rid of a mutation event that is no longer necessary.
+ */
+ void DropMutationEvent(AccTreeMutationEvent* aEvent);
+
+ /**
+ * Fire all necessary mutation events.
+ */
+ void ProcessMutationEvents();
+
+ /**
+ * Indicates whether we're waiting on an event queue processing from our
+ * notification controller to flush events.
+ */
+ enum eObservingState {
+ eNotObservingRefresh,
+ eRefreshObserving,
+ eRefreshProcessing,
+ eRefreshProcessingForUpdate
+ };
+ eObservingState mObservingState;
+
+ /**
+ * The presshell of the document accessible.
+ */
+ nsIPresShell* mPresShell;
+
+ /**
+ * Child documents that needs to be bound to the tree.
+ */
+ nsTArray<RefPtr<DocAccessible> > mHangingChildDocuments;
+
+ /**
+ * Pending accessible tree update notifications for content insertions.
+ */
+ nsClassHashtable<nsRefPtrHashKey<Accessible>,
+ nsTArray<nsCOMPtr<nsIContent>>> mContentInsertions;
+
+ template<class T>
+ class nsCOMPtrHashKey : public PLDHashEntryHdr
+ {
+ public:
+ typedef T* KeyType;
+ typedef const T* KeyTypePointer;
+
+ explicit nsCOMPtrHashKey(const T* aKey) : mKey(const_cast<T*>(aKey)) {}
+ explicit nsCOMPtrHashKey(const nsPtrHashKey<T> &aToCopy) : mKey(aToCopy.mKey) {}
+ ~nsCOMPtrHashKey() { }
+
+ KeyType GetKey() const { return mKey; }
+ bool KeyEquals(KeyTypePointer aKey) const { return aKey == mKey; }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey)
+ { return NS_PTR_TO_INT32(aKey) >> 2; }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ protected:
+ nsCOMPtr<T> mKey;
+ };
+
+ /**
+ * Pending accessible tree update notifications for rendered text changes.
+ */
+ nsTHashtable<nsCOMPtrHashKey<nsIContent> > mTextHash;
+
+ /**
+ * Other notifications like DOM events. Don't make this an AutoTArray; we
+ * use SwapElements() on it.
+ */
+ nsTArray<RefPtr<Notification> > mNotifications;
+
+ /**
+ * Holds all scheduled relocations.
+ */
+ nsTArray<RefPtr<Accessible> > mRelocations;
+
+ /**
+ * Holds all mutation events.
+ */
+ EventTree mEventTree;
+
+ /**
+ * A temporary collection of hide events that should be fired before related
+ * show event. Used by EventTree.
+ */
+ nsTArray<RefPtr<AccHideEvent>> mPrecedingEvents;
+
+#ifdef DEBUG
+ bool mMoveGuardOnStack;
+#endif
+
+ friend class MoveGuard;
+ friend class EventTree;
+
+ /**
+ * A list of all mutation events we may want to emit. Ordered from the first
+ * event that should be emitted to the last one to emit.
+ */
+ RefPtr<AccTreeMutationEvent> mFirstMutationEvent;
+ RefPtr<AccTreeMutationEvent> mLastMutationEvent;
+
+ /**
+ * A class to map an accessible and event type to an event.
+ */
+ class EventMap
+ {
+ public:
+ enum EventType
+ {
+ ShowEvent = 0x0,
+ HideEvent = 0x1,
+ ReorderEvent = 0x2,
+ };
+
+ void PutEvent(AccTreeMutationEvent* aEvent);
+ AccTreeMutationEvent* GetEvent(Accessible* aTarget, EventType aType);
+ void RemoveEvent(AccTreeMutationEvent* aEvent);
+ void Clear() { mTable.Clear(); }
+
+ private:
+ EventType GetEventType(AccTreeMutationEvent* aEvent);
+
+ nsRefPtrHashtable<nsUint64HashKey, AccTreeMutationEvent> mTable;
+ };
+
+ EventMap mMutationMap;
+ uint32_t mEventGeneration;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_NotificationController_h_
diff --git a/accessible/base/Platform.h b/accessible/base/Platform.h
new file mode 100644
index 000000000..25204565b
--- /dev/null
+++ b/accessible/base/Platform.h
@@ -0,0 +1,88 @@
+/* -*- 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_Platform_h
+#define mozilla_a11y_Platform_h
+
+#include <stdint.h>
+
+class nsString;
+
+namespace mozilla {
+namespace a11y {
+
+class ProxyAccessible;
+
+enum EPlatformDisabledState {
+ ePlatformIsForceEnabled = -1,
+ ePlatformIsEnabled = 0,
+ ePlatformIsDisabled = 1
+};
+
+/**
+ * Return the platform disabled state.
+ */
+EPlatformDisabledState PlatformDisabledState();
+
+#ifdef MOZ_ACCESSIBILITY_ATK
+/**
+ * Perform initialization that should be done as soon as possible, in order
+ * to minimize startup time.
+ * XXX: this function and the next defined in ApplicationAccessibleWrap.cpp
+ */
+void PreInit();
+#endif
+
+#if defined(MOZ_ACCESSIBILITY_ATK) || defined(XP_MACOSX)
+/**
+ * Is platform accessibility enabled.
+ * Only used on linux with atk and MacOS for now.
+ */
+bool ShouldA11yBeEnabled();
+#endif
+
+/**
+ * Called to initialize platform specific accessibility support.
+ * Note this is called after internal accessibility support is initialized.
+ */
+void PlatformInit();
+
+/**
+ * Shutdown platform accessibility.
+ * Note this is called before internal accessibility support is shutdown.
+ */
+void PlatformShutdown();
+
+/**
+ * called when a new ProxyAccessible is created, so the platform may setup a
+ * wrapper for it, or take other action.
+ */
+void ProxyCreated(ProxyAccessible* aProxy, uint32_t aInterfaces);
+
+/**
+ * Called just before a ProxyAccessible is destroyed so its wrapper can be
+ * disposed of and other action taken.
+ */
+void ProxyDestroyed(ProxyAccessible*);
+
+/**
+ * Callied when an event is fired on a proxied accessible.
+ */
+void ProxyEvent(ProxyAccessible* aTarget, uint32_t aEventType);
+void ProxyStateChangeEvent(ProxyAccessible* aTarget, uint64_t aState,
+ bool aEnabled);
+void ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset);
+void ProxyTextChangeEvent(ProxyAccessible* aTarget, const nsString& aStr,
+ int32_t aStart, uint32_t aLen, bool aIsInsert,
+ bool aFromUser);
+void ProxyShowHideEvent(ProxyAccessible* aTarget, ProxyAccessible* aParent,
+ bool aInsert, bool aFromUser);
+void ProxySelectionEvent(ProxyAccessible* aTarget, ProxyAccessible* aWidget,
+ uint32_t aType);
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_Platform_h
diff --git a/accessible/base/Relation.h b/accessible/base/Relation.h
new file mode 100644
index 000000000..49a2c692e
--- /dev/null
+++ b/accessible/base/Relation.h
@@ -0,0 +1,108 @@
+/* -*- 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_relation_h_
+#define mozilla_a11y_relation_h_
+
+#include "AccIterator.h"
+
+#include <memory>
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * A collection of relation targets of a certain type. Targets are computed
+ * lazily while enumerating.
+ */
+class Relation
+{
+public:
+ Relation() : mFirstIter(nullptr), mLastIter(nullptr) { }
+
+ explicit Relation(AccIterable* aIter) :
+ mFirstIter(aIter), mLastIter(aIter) { }
+
+ explicit Relation(Accessible* aAcc) :
+ mFirstIter(nullptr), mLastIter(nullptr)
+ { AppendTarget(aAcc); }
+
+ Relation(DocAccessible* aDocument, nsIContent* aContent) :
+ mFirstIter(nullptr), mLastIter(nullptr)
+ { AppendTarget(aDocument, aContent); }
+
+ Relation(Relation&& aOther) :
+ mFirstIter(Move(aOther.mFirstIter)), mLastIter(aOther.mLastIter)
+ {
+ aOther.mLastIter = nullptr;
+ }
+
+ Relation& operator = (Relation&& aRH)
+ {
+ mFirstIter = Move(aRH.mFirstIter);
+ mLastIter = aRH.mLastIter;
+ aRH.mLastIter = nullptr;
+ return *this;
+ }
+
+ inline void AppendIter(AccIterable* aIter)
+ {
+ if (mLastIter)
+ mLastIter->mNextIter.reset(aIter);
+ else
+ mFirstIter.reset(aIter);
+
+ mLastIter = aIter;
+ }
+
+ /**
+ * Append the given accessible to the set of related accessibles.
+ */
+ inline void AppendTarget(Accessible* aAcc)
+ {
+ if (aAcc)
+ AppendIter(new SingleAccIterator(aAcc));
+ }
+
+ /**
+ * Append the one accessible for this content node to the set of related
+ * accessibles.
+ */
+ void AppendTarget(DocAccessible* aDocument, nsIContent* aContent)
+ {
+ if (aContent)
+ AppendTarget(aDocument->GetAccessible(aContent));
+ }
+
+ /**
+ * compute and return the next related accessible.
+ */
+ inline Accessible* Next()
+ {
+ Accessible* target = nullptr;
+
+ while (mFirstIter && !(target = mFirstIter->Next()))
+ mFirstIter = std::move(mFirstIter->mNextIter);
+
+ if (!mFirstIter)
+ mLastIter = nullptr;
+
+ return target;
+ }
+
+private:
+ Relation& operator = (const Relation&) = delete;
+ Relation(const Relation&) = delete;
+
+ std::unique_ptr<AccIterable> mFirstIter;
+ AccIterable* mLastIter;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/base/RelationType.h b/accessible/base/RelationType.h
new file mode 100644
index 000000000..ff2894d3b
--- /dev/null
+++ b/accessible/base/RelationType.h
@@ -0,0 +1,163 @@
+/* -*- 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_relationtype_h_
+#define mozilla_a11y_relationtype_h_
+
+namespace mozilla {
+namespace a11y {
+
+enum class RelationType {
+
+ /**
+ * This object is labelled by a target object.
+ */
+ LABELLED_BY = 0x00,
+
+ /**
+ * This object is label for a target object.
+ */
+ LABEL_FOR = 0x01,
+
+ /**
+ * This object is described by the target object.
+ */
+ DESCRIBED_BY = 0x02,
+
+ /**
+ * This object is describes the target object.
+ */
+ DESCRIPTION_FOR = 0x3,
+
+ /**
+ * This object is a child of a target object.
+ */
+ NODE_CHILD_OF = 0x4,
+
+ /**
+ * This object is a parent of a target object. A dual relation to
+ * NODE_CHILD_OF.
+ */
+ NODE_PARENT_OF = 0x5,
+
+ /**
+ * Some attribute of this object is affected by a target object.
+ */
+ CONTROLLED_BY = 0x06,
+
+ /**
+ * This object is interactive and controls some attribute of a target object.
+ */
+ CONTROLLER_FOR = 0x07,
+
+ /**
+ * Content flows from this object to a target object, i.e. has content that
+ * flows logically to another object in a sequential way, e.g. text flow.
+ */
+ FLOWS_TO = 0x08,
+
+ /**
+ * Content flows to this object from a target object, i.e. has content that
+ * flows logically from another object in a sequential way, e.g. text flow.
+ */
+ FLOWS_FROM = 0x09,
+
+ /**
+ * This object is a member of a group of one or more objects. When there is
+ * more than one object in the group each member may have one and the same
+ * target, e.g. a grouping object. It is also possible that each member has
+ * multiple additional targets, e.g. one for every other member in the group.
+ */
+ MEMBER_OF = 0x0a,
+
+ /**
+ * This object is a sub window of a target object.
+ */
+ SUBWINDOW_OF = 0x0b,
+
+ /**
+ * This object embeds a target object. This relation can be used on the
+ * OBJID_CLIENT accessible for a top level window to show where the content
+ * areas are.
+ */
+ EMBEDS = 0x0c,
+
+ /**
+ * This object is embedded by a target object.
+ */
+ EMBEDDED_BY = 0x0d,
+
+ /**
+ * This object is a transient component related to the target object. When
+ * this object is activated the target object doesn't lose focus.
+ */
+ POPUP_FOR = 0x0e,
+
+ /**
+ * This object is a parent window of the target object.
+ */
+ PARENT_WINDOW_OF = 0x0f,
+
+ /**
+ * Part of a form/dialog with a related default button. It is used for
+ * MSAA/XPCOM, it isn't for IA2 or ATK.
+ */
+ DEFAULT_BUTTON = 0x10,
+
+ /**
+ * The target object is the containing document object.
+ */
+ CONTAINING_DOCUMENT = 0x11,
+
+ /**
+ * The target object is the topmost containing document object in the tab pane.
+ */
+ CONTAINING_TAB_PANE = 0x12,
+
+ /**
+ * The target object is the containing window object.
+ */
+ CONTAINING_WINDOW = 0x13,
+
+ /**
+ * The target object is the containing application object.
+ */
+ CONTAINING_APPLICATION = 0x14,
+
+
+ /**
+ * The target object provides the detailed, extended description for this
+ * object. It provides more detailed information than would normally be
+ * provided using the DESCRIBED_BY relation. A common use for this relation is
+ * in digital publishing where an extended description needs to be conveyed in
+ * a book that requires structural markup or the embedding of other technology
+ * to provide illustrative content.
+ */
+ DETAILS = 0x15,
+
+ /**
+ * This object provides the detailed, extended description for the target
+ * object. See DETAILS relation.
+ */
+ DETAILS_FOR = 0x16,
+
+ /**
+ * The target object is the error message for this object.
+ */
+ ERRORMSG = 0x17,
+
+ /**
+ * This object is the error message for the target object.
+ */
+ ERRORMSG_FOR = 0x18,
+
+ LAST = ERRORMSG_FOR
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/RelationTypeMap.h b/accessible/base/RelationTypeMap.h
new file mode 100644
index 000000000..fb45e42c1
--- /dev/null
+++ b/accessible/base/RelationTypeMap.h
@@ -0,0 +1,154 @@
+/* -*- 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/. */
+
+/**
+ * Usage: declare the macro RELATIONTYPE()with the following arguments:
+ * RELATIONTYPE(geckoType, geckoTypeName, atkType, msaaType, ia2Type)
+ */
+
+RELATIONTYPE(LABELLED_BY,
+ "labelled by",
+ ATK_RELATION_LABELLED_BY,
+ NAVRELATION_LABELLED_BY,
+ IA2_RELATION_LABELLED_BY)
+
+RELATIONTYPE(LABEL_FOR,
+ "label for",
+ ATK_RELATION_LABEL_FOR,
+ NAVRELATION_LABEL_FOR,
+ IA2_RELATION_LABEL_FOR)
+
+RELATIONTYPE(DESCRIBED_BY,
+ "described by",
+ ATK_RELATION_DESCRIBED_BY,
+ NAVRELATION_DESCRIBED_BY,
+ IA2_RELATION_DESCRIBED_BY)
+
+RELATIONTYPE(DESCRIPTION_FOR,
+ "description for",
+ ATK_RELATION_DESCRIPTION_FOR,
+ NAVRELATION_DESCRIPTION_FOR,
+ IA2_RELATION_DESCRIPTION_FOR)
+
+RELATIONTYPE(NODE_CHILD_OF,
+ "node child of",
+ ATK_RELATION_NODE_CHILD_OF,
+ NAVRELATION_NODE_CHILD_OF,
+ IA2_RELATION_NODE_CHILD_OF)
+
+RELATIONTYPE(NODE_PARENT_OF,
+ "node parent of",
+ ATK_RELATION_NODE_PARENT_OF,
+ NAVRELATION_NODE_PARENT_OF,
+ IA2_RELATION_NODE_PARENT_OF)
+
+RELATIONTYPE(CONTROLLED_BY,
+ "controlled by",
+ ATK_RELATION_CONTROLLED_BY,
+ NAVRELATION_CONTROLLED_BY,
+ IA2_RELATION_CONTROLLED_BY)
+
+RELATIONTYPE(CONTROLLER_FOR,
+ "controller for",
+ ATK_RELATION_CONTROLLER_FOR,
+ NAVRELATION_CONTROLLER_FOR,
+ IA2_RELATION_CONTROLLER_FOR)
+
+RELATIONTYPE(FLOWS_TO,
+ "flows to",
+ ATK_RELATION_FLOWS_TO,
+ NAVRELATION_FLOWS_TO,
+ IA2_RELATION_FLOWS_TO)
+
+RELATIONTYPE(FLOWS_FROM,
+ "flows from",
+ ATK_RELATION_FLOWS_FROM,
+ NAVRELATION_FLOWS_FROM,
+ IA2_RELATION_FLOWS_FROM)
+
+RELATIONTYPE(MEMBER_OF,
+ "member of",
+ ATK_RELATION_MEMBER_OF,
+ NAVRELATION_MEMBER_OF,
+ IA2_RELATION_MEMBER_OF)
+
+RELATIONTYPE(SUBWINDOW_OF,
+ "subwindow of",
+ ATK_RELATION_SUBWINDOW_OF,
+ NAVRELATION_SUBWINDOW_OF,
+ IA2_RELATION_SUBWINDOW_OF)
+
+RELATIONTYPE(EMBEDS,
+ "embeds",
+ ATK_RELATION_EMBEDS,
+ NAVRELATION_EMBEDS,
+ IA2_RELATION_EMBEDS)
+
+RELATIONTYPE(EMBEDDED_BY,
+ "embedded by",
+ ATK_RELATION_EMBEDDED_BY,
+ NAVRELATION_EMBEDDED_BY,
+ IA2_RELATION_EMBEDDED_BY)
+
+RELATIONTYPE(POPUP_FOR,
+ "popup for",
+ ATK_RELATION_POPUP_FOR,
+ NAVRELATION_POPUP_FOR,
+ IA2_RELATION_POPUP_FOR)
+
+RELATIONTYPE(PARENT_WINDOW_OF,
+ "parent window of",
+ ATK_RELATION_PARENT_WINDOW_OF,
+ NAVRELATION_PARENT_WINDOW_OF,
+ IA2_RELATION_PARENT_WINDOW_OF)
+
+RELATIONTYPE(DEFAULT_BUTTON,
+ "default button",
+ ATK_RELATION_NULL,
+ NAVRELATION_DEFAULT_BUTTON,
+ IA2_RELATION_NULL)
+
+RELATIONTYPE(CONTAINING_DOCUMENT,
+ "containing document",
+ ATK_RELATION_NULL,
+ NAVRELATION_CONTAINING_DOCUMENT,
+ IA2_RELATION_CONTAINING_DOCUMENT)
+
+RELATIONTYPE(CONTAINING_TAB_PANE,
+ "containing tab pane",
+ ATK_RELATION_NULL,
+ NAVRELATION_CONTAINING_TAB_PANE,
+ IA2_RELATION_CONTAINING_TAB_PANE)
+
+RELATIONTYPE(CONTAINING_APPLICATION,
+ "containing application",
+ ATK_RELATION_NULL,
+ NAVRELATION_CONTAINING_APPLICATION,
+ IA2_RELATION_CONTAINING_APPLICATION)
+
+RELATIONTYPE(DETAILS,
+ "details",
+ ATK_RELATION_NULL,
+ NAVRELATION_DETAILS,
+ IA2_RELATION_DETAILS)
+
+RELATIONTYPE(DETAILS_FOR,
+ "details for",
+ ATK_RELATION_NULL,
+ NAVRELATION_DETAILS_FOR,
+ IA2_RELATION_DETAILS_FOR)
+
+RELATIONTYPE(ERRORMSG,
+ "error",
+ ATK_RELATION_NULL,
+ NAVRELATION_ERROR,
+ IA2_RELATION_ERROR)
+
+RELATIONTYPE(ERRORMSG_FOR,
+ "error for",
+ ATK_RELATION_NULL,
+ NAVRELATION_ERROR_FOR,
+ IA2_RELATION_ERROR_FOR)
diff --git a/accessible/base/Role.h b/accessible/base/Role.h
new file mode 100644
index 000000000..6d76eebd7
--- /dev/null
+++ b/accessible/base/Role.h
@@ -0,0 +1,995 @@
+/* -*- 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 _role_h_
+#define _role_h_
+
+/**
+ * @note Make sure to update the localized role names when changing the list.
+ * @note When adding a new role, be sure to also add it to base/RoleMap.h and
+ * update nsIAccessibleRole.
+ */
+
+namespace mozilla {
+namespace a11y {
+namespace roles {
+
+enum Role {
+ /**
+ * Used when accessible hans't strong defined role.
+ */
+ NOTHING = 0,
+
+ /**
+ * Represents a title or caption bar for a window. It is used by MSAA only,
+ * supported automatically by MS Windows.
+ */
+ TITLEBAR = 1,
+
+ /**
+ * Represents the menu bar (positioned beneath the title bar of a window)
+ * from which menus are selected by the user. The role is used by
+ * xul:menubar or role="menubar".
+ */
+ MENUBAR = 2,
+
+ /**
+ * Represents a vertical or horizontal scroll bar, which is part of the client
+ * area or used in a control.
+ */
+ SCROLLBAR = 3,
+
+ /**
+ * Represents a special mouse pointer, which allows a user to manipulate user
+ * interface elements such as windows. For example, a user clicks and drags
+ * a sizing grip in the lower-right corner of a window to resize it.
+ */
+ GRIP = 4,
+
+ /**
+ * Represents a system sound, which is associated with various system events.
+ */
+ SOUND = 5,
+
+ /**
+ * Represents the system mouse pointer.
+ */
+ CURSOR = 6,
+
+ /**
+ * Represents the system caret. The role is supported for caret.
+ */
+ CARET = 7,
+
+ /**
+ * Represents an alert or a condition that a user should be notified about.
+ * Assistive Technologies typically respond to the role by reading the entire
+ * onscreen contents of containers advertising this role. Should be used for
+ * warning dialogs, etc. The role is used by xul:browsermessage,
+ * role="alert".
+ */
+ ALERT = 8,
+
+ /**
+ * Represents the window frame, which contains child objects such as
+ * a title bar, client, and other objects contained in a window. The role
+ * is supported automatically by MS Windows.
+ */
+ WINDOW = 9,
+
+ /**
+ * A sub-document (<frame> or <iframe>)
+ */
+ INTERNAL_FRAME = 10,
+
+ /**
+ * Represents a menu, which presents a list of options from which the user can
+ * make a selection to perform an action. It is used for role="menu".
+ */
+ MENUPOPUP = 11,
+
+ /**
+ * Represents a menu item, which is an entry in a menu that a user can choose
+ * to carry out a command, select an option. It is used for xul:menuitem,
+ * role="menuitem".
+ */
+ MENUITEM = 12,
+
+ /**
+ * Represents a ToolTip that provides helpful hints.
+ */
+ TOOLTIP = 13,
+
+ /**
+ * Represents a main window for an application. It is used for
+ * role="application". Also refer to APP_ROOT
+ */
+ APPLICATION = 14,
+
+ /**
+ * Represents a document window. A document window is always contained within
+ * an application window. It is used for role="document".
+ */
+ DOCUMENT = 15,
+
+ /**
+ * Represents a pane within a frame or document window. Users can navigate
+ * between panes and within the contents of the current pane, but cannot
+ * navigate between items in different panes. Thus, panes represent a level
+ * of grouping lower than frame windows or documents, but above individual
+ * controls. It is used for the first child of a <frame> or <iframe>.
+ */
+ PANE = 16,
+
+ /**
+ * Represents a graphical image used to represent data.
+ */
+ CHART = 17,
+
+ /**
+ * Represents a dialog box or message box. It is used for xul:dialog,
+ * role="dialog".
+ */
+ DIALOG = 18,
+
+ /**
+ * Represents a window border.
+ */
+ BORDER = 19,
+
+ /**
+ * Logically groups other objects. There is not always a parent-child
+ * relationship between the grouping object and the objects it contains. It
+ * is used for html:textfield, xul:groupbox, role="group".
+ */
+ GROUPING = 20,
+
+ /**
+ * Used to visually divide a space into two regions, such as a separator menu
+ * item or a bar that divides split panes within a window. It is used for
+ * xul:separator, html:hr, role="separator".
+ */
+ SEPARATOR = 21,
+
+ /**
+ * Represents a toolbar, which is a grouping of controls (push buttons or
+ * toggle buttons) that provides easy access to frequently used features. It
+ * is used for xul:toolbar, role="toolbar".
+ */
+ TOOLBAR = 22,
+
+ /**
+ * Represents a status bar, which is an area at the bottom of a window that
+ * displays information about the current operation, state of the application,
+ * or selected object. The status bar has multiple fields, which display
+ * different kinds of information. It is used for xul:statusbar.
+ */
+ STATUSBAR = 23,
+
+ /**
+ * Represents a table that contains rows and columns of cells, and optionally,
+ * row headers and column headers. It is used for html:table,
+ * role="grid". Also refer to the following role: COLUMNHEADER,
+ * ROWHEADER, COLUMN, ROW, CELL.
+ */
+ TABLE = 24,
+
+ /**
+ * Represents a column header, providing a visual label for a column in
+ * a table. It is used for XUL tree column headers, html:th,
+ * role="colheader". Also refer to TABLE.
+ */
+ COLUMNHEADER = 25,
+
+ /**
+ * Represents a row header, which provides a visual label for a table row.
+ * It is used for role="rowheader". Also, see TABLE.
+ */
+ ROWHEADER = 26,
+
+ /**
+ * Represents a column of cells within a table. Also, see TABLE.
+ */
+ COLUMN = 27,
+
+ /**
+ * Represents a row of cells within a table. Also, see TABLE.
+ */
+ ROW = 28,
+
+ /**
+ * Represents a cell within a table. It is used for html:td,
+ * xul:tree cell and xul:listcell. Also, see TABLE.
+ */
+ CELL = 29,
+
+ /**
+ * Represents a link to something else. This object might look like text or
+ * a graphic, but it acts like a button. It is used for
+ * xul:label@class="text-link", html:a, html:area.
+ */
+ LINK = 30,
+
+ /**
+ * Displays a Help topic in the form of a ToolTip or Help balloon.
+ */
+ HELPBALLOON = 31,
+
+ /**
+ * Represents a cartoon-like graphic object, such as Microsoft Office
+ * Assistant, which is displayed to provide help to users of an application.
+ */
+ CHARACTER = 32,
+
+ /**
+ * Represents a list box, allowing the user to select one or more items. It
+ * is used for xul:listbox, html:select@size, role="list". See also
+ * LIST_ITEM.
+ */
+ LIST = 33,
+
+ /**
+ * Represents an item in a list. See also LIST.
+ */
+ LISTITEM = 34,
+
+ /**
+ * Represents an outline or tree structure, such as a tree view control,
+ * that displays a hierarchical list and allows the user to expand and
+ * collapse branches. Is is used for role="tree".
+ */
+ OUTLINE = 35,
+
+ /**
+ * Represents an item in an outline or tree structure. It is used for
+ * role="treeitem".
+ */
+ OUTLINEITEM = 36,
+
+ /**
+ * Represents a page tab, it is a child of a page tab list. It is used for
+ * xul:tab, role="treeitem". Also refer to PAGETABLIST.
+ */
+ PAGETAB = 37,
+
+ /**
+ * Represents a property sheet. It is used for xul:tabpanel,
+ * role="tabpanel".
+ */
+ PROPERTYPAGE = 38,
+
+ /**
+ * Represents an indicator, such as a pointer graphic, that points to the
+ * current item.
+ */
+ INDICATOR = 39,
+
+ /**
+ * Represents a picture. Is is used for xul:image, html:img.
+ */
+ GRAPHIC = 40,
+
+ /**
+ * Represents read-only text, such as labels for other controls or
+ * instructions in a dialog box. Static text cannot be modified or selected.
+ * Is is used for xul:label, xul:description, html:label, role="label".
+ */
+ STATICTEXT = 41,
+
+ /**
+ * Represents selectable text that allows edits or is designated read-only.
+ */
+ TEXT_LEAF = 42,
+
+ /**
+ * Represents a push button control. It is used for xul:button, html:button,
+ * role="button".
+ */
+ PUSHBUTTON = 43,
+
+ /**
+ * Represents a check box control. It is used for xul:checkbox,
+ * html:input@type="checkbox", role="checkbox".
+ */
+ CHECKBUTTON = 44,
+
+ /**
+ * Represents an option button, also called a radio button. It is one of a
+ * group of mutually exclusive options. All objects sharing a single parent
+ * that have this attribute are assumed to be part of single mutually
+ * exclusive group. It is used for xul:radio, html:input@type="radio",
+ * role="radio".
+ */
+ RADIOBUTTON = 45,
+
+ /**
+ * Represents a combo box; an edit control with an associated list box that
+ * provides a set of predefined choices. It is used for html:select,
+ * xul:menulist, role="combobox".
+ */
+ COMBOBOX = 46,
+
+ /**
+ * Represents the calendar control.
+ */
+ DROPLIST = 47,
+
+ /**
+ * Represents a progress bar, dynamically showing the user the percent
+ * complete of an operation in progress. It is used for xul:progressmeter,
+ * role="progressbar".
+ */
+ PROGRESSBAR = 48,
+
+ /**
+ * Represents a dial or knob whose purpose is to allow a user to set a value.
+ */
+ DIAL = 49,
+
+ /**
+ * Represents a hot-key field that allows the user to enter a combination or
+ * sequence of keystrokes.
+ */
+ HOTKEYFIELD = 50,
+
+ /**
+ * Represents a slider, which allows the user to adjust a setting in given
+ * increments between minimum and maximum values. It is used by xul:scale,
+ * role="slider".
+ */
+ SLIDER = 51,
+
+ /**
+ * Represents a spin box, which is a control that allows the user to increment
+ * or decrement the value displayed in a separate "buddy" control associated
+ * with the spin box. It is used for xul:spinbuttons.
+ */
+ SPINBUTTON = 52,
+
+ /**
+ * Represents a graphical image used to diagram data. It is used for svg:svg.
+ */
+ DIAGRAM = 53,
+
+ /**
+ * Represents an animation control, which contains content that changes over
+ * time, such as a control that displays a series of bitmap frames.
+ */
+ ANIMATION = 54,
+
+ /**
+ * Represents a mathematical equation. It is used by MATHML, where there is a
+ * rich DOM subtree for an equation. Use FLAT_EQUATION for <img role="math" alt="[TeX]"/>
+ */
+ EQUATION = 55,
+
+ /**
+ * Represents a button that drops down a list of items.
+ */
+ BUTTONDROPDOWN = 56,
+
+ /**
+ * Represents a button that drops down a menu.
+ */
+ BUTTONMENU = 57,
+
+ /**
+ * Represents a button that drops down a grid. It is used for xul:colorpicker.
+ */
+ BUTTONDROPDOWNGRID = 58,
+
+ /**
+ * Represents blank space between other objects.
+ */
+ WHITESPACE = 59,
+
+ /**
+ * Represents a container of page tab controls. Is it used for xul:tabs,
+ * DHTML: role="tabs". Also refer to PAGETAB.
+ */
+ PAGETABLIST = 60,
+
+ /**
+ * Represents a control that displays time.
+ */
+ CLOCK = 61,
+
+ /**
+ * Represents a button on a toolbar that has a drop-down list icon directly
+ * adjacent to the button.
+ */
+ SPLITBUTTON = 62,
+
+ /**
+ * Represents an edit control designed for an Internet Protocol (IP) address.
+ * The edit control is divided into sections for the different parts of the
+ * IP address.
+ */
+ IPADDRESS = 63,
+
+ /**
+ * Represents a label control that has an accelerator.
+ */
+ ACCEL_LABEL = 64,
+
+ /**
+ * Represents an arrow in one of the four cardinal directions.
+ */
+ ARROW = 65,
+
+ /**
+ * Represents a control that can be drawn into and is used to trap events.
+ * It is used for html:canvas.
+ */
+ CANVAS = 66,
+
+ /**
+ * Represents a menu item with a check box.
+ */
+ CHECK_MENU_ITEM = 67,
+
+ /**
+ * Represents a specialized dialog that lets the user choose a color.
+ */
+ COLOR_CHOOSER = 68,
+
+ /**
+ * Represents control whose purpose is to allow a user to edit a date.
+ */
+ DATE_EDITOR = 69,
+
+ /**
+ * An iconified internal frame in an DESKTOP_PANE. Also refer to
+ * INTERNAL_FRAME.
+ */
+ DESKTOP_ICON = 70,
+
+ /**
+ * A desktop pane. A pane that supports internal frames and iconified
+ * versions of those internal frames.
+ */
+ DESKTOP_FRAME = 71,
+
+ /**
+ * A directory pane. A pane that allows the user to navigate through
+ * and select the contents of a directory. May be used by a file chooser.
+ * Also refer to FILE_CHOOSER.
+ */
+ DIRECTORY_PANE = 72,
+
+ /**
+ * A file chooser. A specialized dialog that displays the files in the
+ * directory and lets the user select a file, browse a different directory,
+ * or specify a filename. May use the directory pane to show the contents of
+ * a directory. Also refer to DIRECTORY_PANE.
+ */
+ FILE_CHOOSER = 73,
+
+ /**
+ * A font chooser. A font chooser is a component that lets the user pick
+ * various attributes for fonts.
+ */
+ FONT_CHOOSER = 74,
+
+ /**
+ * Frame role. A top level window with a title bar, border, menu bar, etc.
+ * It is often used as the primary window for an application.
+ */
+ CHROME_WINDOW = 75,
+
+ /**
+ * A glass pane. A pane that is guaranteed to be painted on top of all
+ * panes beneath it. Also refer to ROOT_PANE.
+ */
+ GLASS_PANE = 76,
+
+ /**
+ * A document container for HTML, whose children represent the document
+ * content.
+ */
+ HTML_CONTAINER = 77,
+
+ /**
+ * A small fixed size picture, typically used to decorate components.
+ */
+ ICON = 78,
+
+ /**
+ * Presents an icon or short string in an interface.
+ */
+ LABEL = 79,
+
+ /**
+ * A layered pane. A specialized pane that allows its children to be drawn
+ * in layers, providing a form of stacking order. This is usually the pane
+ * that holds the menu bar as well as the pane that contains most of the
+ * visual components in a window. Also refer to GLASS_PANE and
+ * ROOT_PANE.
+ */
+ LAYERED_PANE = 80,
+
+ /**
+ * A specialized pane whose primary use is inside a dialog.
+ */
+ OPTION_PANE = 81,
+
+ /**
+ * A text object uses for passwords, or other places where the text content
+ * is not shown visibly to the user.
+ */
+ PASSWORD_TEXT = 82,
+
+ /**
+ * A temporary window that is usually used to offer the user a list of
+ * choices, and then hides when the user selects one of those choices.
+ */
+ POPUP_MENU = 83,
+
+ /**
+ * A radio button that is a menu item.
+ */
+ RADIO_MENU_ITEM = 84,
+
+ /**
+ * A root pane. A specialized pane that has a glass pane and a layered pane
+ * as its children. Also refer to GLASS_PANE and LAYERED_PANE.
+ */
+ ROOT_PANE = 85,
+
+ /**
+ * A scroll pane. An object that allows a user to incrementally view a large
+ * amount of information. Its children can include scroll bars and a
+ * viewport. Also refer to VIEW_PORT.
+ */
+ SCROLL_PANE = 86,
+
+ /**
+ * A split pane. A specialized panel that presents two other panels at the
+ * same time. Between the two panels is a divider the user can manipulate to
+ * make one panel larger and the other panel smaller.
+ */
+ SPLIT_PANE = 87,
+
+ /**
+ * The header for a column of a table.
+ * XXX: it looks this role is dupe of COLUMNHEADER.
+ */
+ TABLE_COLUMN_HEADER = 88,
+
+ /**
+ * The header for a row of a table.
+ * XXX: it looks this role is dupe of ROWHEADER
+ */
+ TABLE_ROW_HEADER = 89,
+
+ /**
+ * A menu item used to tear off and reattach its menu.
+ */
+ TEAR_OFF_MENU_ITEM = 90,
+
+ /**
+ * Represents an accessible terminal.
+ */
+ TERMINAL = 91,
+
+ /**
+ * Collection of objects that constitute a logical text entity.
+ */
+ TEXT_CONTAINER = 92,
+
+ /**
+ * A toggle button. A specialized push button that can be checked or
+ * unchecked, but does not provide a separate indicator for the current state.
+ */
+ TOGGLE_BUTTON = 93,
+
+ /**
+ * Represent a control that is capable of expanding and collapsing rows as
+ * well as showing multiple columns of data.
+ */
+ TREE_TABLE = 94,
+
+ /**
+ * A viewport. An object usually used in a scroll pane. It represents the
+ * portion of the entire data that the user can see. As the user manipulates
+ * the scroll bars, the contents of the viewport can change. Also refer to
+ * SCROLL_PANE.
+ */
+ VIEWPORT = 95,
+
+ /**
+ * Header of a document page. Also refer to FOOTER.
+ */
+ HEADER = 96,
+
+ /**
+ * Footer of a document page. Also refer to HEADER.
+ */
+ FOOTER = 97,
+
+ /**
+ * A paragraph of text.
+ */
+ PARAGRAPH = 98,
+
+ /**
+ * A ruler such as those used in word processors.
+ */
+ RULER = 99,
+
+ /**
+ * A text entry having dialog or list containing items for insertion into
+ * an entry widget, for instance a list of words for completion of a
+ * text entry. It is used for xul:textbox@autocomplete
+ */
+ AUTOCOMPLETE = 100,
+
+ /**
+ * An editable text object in a toolbar.
+ */
+ EDITBAR = 101,
+
+ /**
+ * An control whose textual content may be entered or modified by the user.
+ */
+ ENTRY = 102,
+
+ /**
+ * A caption describing another object.
+ */
+ CAPTION = 103,
+
+ /**
+ * A visual frame or container which contains a view of document content.
+ * Document frames may occur within another Document instance, in which case
+ * the second document may be said to be embedded in the containing instance.
+ * HTML frames are often DOCUMENT_FRAME. Either this object, or a
+ * singleton descendant, should implement the Document interface.
+ */
+ DOCUMENT_FRAME = 104,
+
+ /**
+ * Heading.
+ */
+ HEADING = 105,
+
+ /**
+ * An object representing a page of document content. It is used in documents
+ * which are accessed by the user on a page by page basis.
+ */
+ PAGE = 106,
+
+ /**
+ * A container of document content. An example of the use of this role is to
+ * represent an html:div.
+ */
+ SECTION = 107,
+
+ /**
+ * An object which is redundant with another object in the accessible
+ * hierarchy. ATs typically ignore objects with this role.
+ */
+ REDUNDANT_OBJECT = 108,
+
+ /**
+ * A container of form controls. An example of the use of this role is to
+ * represent an html:form.
+ */
+ FORM = 109,
+
+ /**
+ * An object which is used to allow input of characters not found on a
+ * keyboard, such as the input of Chinese characters on a Western keyboard.
+ */
+ IME = 110,
+
+ /**
+ * XXX: document this.
+ */
+ APP_ROOT = 111,
+
+ /**
+ * Represents a menu item, which is an entry in a menu that a user can choose
+ * to display another menu.
+ */
+ PARENT_MENUITEM = 112,
+
+ /**
+ * A calendar that allows the user to select a date.
+ */
+ CALENDAR = 113,
+
+ /**
+ * A list of items that is shown by combobox.
+ */
+ COMBOBOX_LIST = 114,
+
+ /**
+ * A item of list that is shown by combobox.
+ */
+ COMBOBOX_OPTION = 115,
+
+ /**
+ * An image map -- has child links representing the areas
+ */
+ IMAGE_MAP = 116,
+
+ /**
+ * An option in a listbox
+ */
+ OPTION = 117,
+
+ /**
+ * A rich option in a listbox, it can have other widgets as children
+ */
+ RICH_OPTION = 118,
+
+ /**
+ * A list of options
+ */
+ LISTBOX = 119,
+
+ /**
+ * Represents a mathematical equation in the accessible name
+ */
+ FLAT_EQUATION = 120,
+
+ /**
+ * Represents a cell within a grid. It is used for role="gridcell". Unlike
+ * CELL, it allows the calculation of the accessible name from subtree.
+ * Also, see TABLE.
+ */
+ GRID_CELL = 121,
+
+ /**
+ * Represents an embedded object. It is used for html:object or html:embed.
+ */
+ EMBEDDED_OBJECT = 122,
+
+ /**
+ * A note. Originally intended to be hidden until activated, but now also used
+ * for things like html 'aside'.
+ */
+ NOTE = 123,
+
+ /**
+ * A figure. Used for things like HTML5 figure element.
+ */
+ FIGURE = 124,
+
+ /**
+ * Represents a rich item with a check box.
+ */
+ CHECK_RICH_OPTION = 125,
+
+ /**
+ * Represent a definition list (dl in HTML).
+ */
+ DEFINITION_LIST = 126,
+
+ /**
+ * Represent a term in a definition list (dt in HTML).
+ */
+ TERM = 127,
+
+ /**
+ * Represent a definition in a definition list (dd in HTML)
+ */
+ DEFINITION = 128,
+
+ /**
+ * Represent a keyboard or keypad key (ARIA role "key").
+ */
+ KEY = 129,
+
+ /**
+ * Represent a switch control widget (ARIA role "switch").
+ */
+ SWITCH = 130,
+
+ /**
+ * A block of MathML code (math).
+ */
+ MATHML_MATH = 131,
+
+ /**
+ * A MathML identifier (mi in MathML).
+ */
+ MATHML_IDENTIFIER = 132,
+
+ /**
+ * A MathML number (mn in MathML).
+ */
+ MATHML_NUMBER = 133,
+
+ /**
+ * A MathML operator (mo in MathML).
+ */
+ MATHML_OPERATOR = 134,
+
+ /**
+ * A MathML text (mtext in MathML).
+ */
+ MATHML_TEXT = 135,
+
+ /**
+ * A MathML string literal (ms in MathML).
+ */
+ MATHML_STRING_LITERAL = 136,
+
+ /**
+ * A MathML glyph (mglyph in MathML).
+ */
+ MATHML_GLYPH = 137,
+
+ /**
+ * A MathML row (mrow in MathML).
+ */
+ MATHML_ROW = 138,
+
+ /**
+ * A MathML fraction (mfrac in MathML).
+ */
+ MATHML_FRACTION = 139,
+
+ /**
+ * A MathML square root (msqrt in MathML).
+ */
+ MATHML_SQUARE_ROOT = 140,
+
+ /**
+ * A MathML root (mroot in MathML).
+ */
+ MATHML_ROOT = 141,
+
+ /**
+ * A MathML fenced element (mfenced in MathML).
+ */
+ MATHML_FENCED = 142,
+
+ /**
+ * A MathML enclosed element (menclose in MathML).
+ */
+ MATHML_ENCLOSED = 143,
+
+ /**
+ * A MathML styling element (mstyle in MathML).
+ */
+ MATHML_STYLE = 144,
+
+ /**
+ * A MathML subscript (msub in MathML).
+ */
+ MATHML_SUB = 145,
+
+ /**
+ * A MathML superscript (msup in MathML).
+ */
+ MATHML_SUP = 146,
+
+ /**
+ * A MathML subscript and superscript (msubsup in MathML).
+ */
+ MATHML_SUB_SUP = 147,
+
+ /**
+ * A MathML underscript (munder in MathML).
+ */
+ MATHML_UNDER = 148,
+
+ /**
+ * A MathML overscript (mover in MathML).
+ */
+ MATHML_OVER = 149,
+
+ /**
+ * A MathML underscript and overscript (munderover in MathML).
+ */
+ MATHML_UNDER_OVER = 150,
+
+ /**
+ * A MathML multiple subscript and superscript element (mmultiscripts in
+ * MathML).
+ */
+ MATHML_MULTISCRIPTS = 151,
+
+ /**
+ * A MathML table (mtable in MathML).
+ */
+ MATHML_TABLE = 152,
+
+ /**
+ * A MathML labelled table row (mlabeledtr in MathML).
+ */
+ MATHML_LABELED_ROW = 153,
+
+ /**
+ * A MathML table row (mtr in MathML).
+ */
+ MATHML_TABLE_ROW = 154,
+
+ /**
+ * A MathML table entry or cell (mtd in MathML).
+ */
+ MATHML_CELL = 155,
+
+ /**
+ * A MathML interactive element (maction in MathML).
+ */
+ MATHML_ACTION = 156,
+
+ /**
+ * A MathML error message (merror in MathML).
+ */
+ MATHML_ERROR = 157,
+
+ /**
+ * A MathML stacked (rows of numbers) element (mstack in MathML).
+ */
+ MATHML_STACK = 158,
+
+ /**
+ * A MathML long division element (mlongdiv in MathML).
+ */
+ MATHML_LONG_DIVISION = 159,
+
+ /**
+ * A MathML stack group (msgroup in MathML).
+ */
+ MATHML_STACK_GROUP = 160,
+
+ /**
+ * A MathML stack row (msrow in MathML).
+ */
+ MATHML_STACK_ROW = 161,
+
+ /**
+ * MathML carries, borrows, or crossouts for a row (mscarries in MathML).
+ */
+ MATHML_STACK_CARRIES = 162,
+
+ /**
+ * A MathML carry, borrow, or crossout for a column (mscarry in MathML).
+ */
+ MATHML_STACK_CARRY = 163,
+
+ /**
+ * A MathML line in a stack (msline in MathML).
+ */
+ MATHML_STACK_LINE = 164,
+
+ /**
+ * A group containing radio buttons
+ */
+ RADIO_GROUP = 165,
+
+ /**
+ * A text container exposing brief amount of information. See related
+ * TEXT_CONTAINER role.
+ */
+ TEXT = 166,
+
+ /**
+ * The html:details element.
+ */
+ DETAILS = 167,
+
+ /**
+ * The html:summary element.
+ */
+ SUMMARY = 168,
+
+ LAST_ROLE = SUMMARY
+};
+
+} // namespace role
+
+typedef enum mozilla::a11y::roles::Role role;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/RoleMap.h b/accessible/base/RoleMap.h
new file mode 100644
index 000000000..c931355ae
--- /dev/null
+++ b/accessible/base/RoleMap.h
@@ -0,0 +1,1370 @@
+/* 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/. */
+
+/**
+ * Usage: declare the macro ROLE()with the following arguments:
+ * ROLE(geckoRole, stringRole, atkRole, macRole, msaaRole, ia2Role, nameRule)
+ */
+
+ROLE(NOTHING,
+ "nothing",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_UNKNOWN,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(TITLEBAR,
+ "titlebar",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole, //Irrelevant on OS X; windows are always native.
+ ROLE_SYSTEM_TITLEBAR,
+ ROLE_SYSTEM_TITLEBAR,
+ eNoNameRule)
+
+ROLE(MENUBAR,
+ "menubar",
+ ATK_ROLE_MENU_BAR,
+ NSAccessibilityMenuBarRole, //Irrelevant on OS X; the menubar will always be native and on the top of the screen.
+ ROLE_SYSTEM_MENUBAR,
+ ROLE_SYSTEM_MENUBAR,
+ eNoNameRule)
+
+ROLE(SCROLLBAR,
+ "scrollbar",
+ ATK_ROLE_SCROLL_BAR,
+ NSAccessibilityScrollBarRole, //We might need to make this its own mozAccessible, to support the children objects (valueindicator, down/up buttons).
+ ROLE_SYSTEM_SCROLLBAR,
+ ROLE_SYSTEM_SCROLLBAR,
+ eNameFromValueRule)
+
+ROLE(GRIP,
+ "grip",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilitySplitterRole,
+ ROLE_SYSTEM_GRIP,
+ ROLE_SYSTEM_GRIP,
+ eNoNameRule)
+
+ROLE(SOUND,
+ "sound",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole, //Unused on OS X.
+ ROLE_SYSTEM_SOUND,
+ ROLE_SYSTEM_SOUND,
+ eNoNameRule)
+
+ROLE(CURSOR,
+ "cursor",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole, //Unused on OS X.
+ ROLE_SYSTEM_CURSOR,
+ ROLE_SYSTEM_CURSOR,
+ eNoNameRule)
+
+ROLE(CARET,
+ "caret",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole, //Unused on OS X.
+ ROLE_SYSTEM_CARET,
+ ROLE_SYSTEM_CARET,
+ eNoNameRule)
+
+ROLE(ALERT,
+ "alert",
+ ATK_ROLE_ALERT,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_ALERT,
+ ROLE_SYSTEM_ALERT,
+ eNoNameRule)
+
+ROLE(WINDOW,
+ "window",
+ ATK_ROLE_WINDOW,
+ NSAccessibilityWindowRole, //Irrelevant on OS X; all window a11y is handled by the system.
+ ROLE_SYSTEM_WINDOW,
+ ROLE_SYSTEM_WINDOW,
+ eNoNameRule)
+
+ROLE(INTERNAL_FRAME,
+ "internal frame",
+ ATK_ROLE_INTERNAL_FRAME,
+ NSAccessibilityScrollAreaRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_INTERNAL_FRAME,
+ eNoNameRule)
+
+ROLE(MENUPOPUP,
+ "menupopup",
+ ATK_ROLE_MENU,
+ NSAccessibilityMenuRole, //The parent of menuitems.
+ ROLE_SYSTEM_MENUPOPUP,
+ ROLE_SYSTEM_MENUPOPUP,
+ eNoNameRule)
+
+ROLE(MENUITEM,
+ "menuitem",
+ ATK_ROLE_MENU_ITEM,
+ NSAccessibilityMenuItemRole,
+ ROLE_SYSTEM_MENUITEM,
+ ROLE_SYSTEM_MENUITEM,
+ eNameFromSubtreeRule)
+
+ROLE(TOOLTIP,
+ "tooltip",
+ ATK_ROLE_TOOL_TIP,
+ @"AXHelpTag", //10.4+ only, so we re-define the constant.
+ ROLE_SYSTEM_TOOLTIP,
+ ROLE_SYSTEM_TOOLTIP,
+ eNameFromSubtreeRule)
+
+ROLE(APPLICATION,
+ "application",
+ ATK_ROLE_EMBEDDED,
+ NSAccessibilityGroupRole, //Unused on OS X. the system will take care of this.
+ ROLE_SYSTEM_APPLICATION,
+ ROLE_SYSTEM_APPLICATION,
+ eNoNameRule)
+
+ROLE(DOCUMENT,
+ "document",
+ ATK_ROLE_DOCUMENT_FRAME,
+ @"AXWebArea",
+ ROLE_SYSTEM_DOCUMENT,
+ ROLE_SYSTEM_DOCUMENT,
+ eNoNameRule)
+
+/**
+ * msaa comment:
+ * We used to map to ROLE_SYSTEM_PANE, but JAWS would
+ * not read the accessible name for the contaning pane.
+ * However, JAWS will read the accessible name for a groupbox.
+ * By mapping a PANE to a GROUPING, we get no undesirable effects,
+ * but fortunately JAWS will then read the group's label,
+ * when an inner control gets focused.
+ */
+ROLE(PANE,
+ "pane",
+ ATK_ROLE_PANEL,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_GROUPING,
+ ROLE_SYSTEM_GROUPING,
+ eNoNameRule)
+
+ROLE(CHART,
+ "chart",
+ ATK_ROLE_CHART,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_CHART,
+ ROLE_SYSTEM_CHART,
+ eNoNameRule)
+
+ROLE(DIALOG,
+ "dialog",
+ ATK_ROLE_DIALOG,
+ NSAccessibilityWindowRole, //There's a dialog subrole.
+ ROLE_SYSTEM_DIALOG,
+ ROLE_SYSTEM_DIALOG,
+ eNoNameRule)
+
+ROLE(BORDER,
+ "border",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole, //Unused on OS X.
+ ROLE_SYSTEM_BORDER,
+ ROLE_SYSTEM_BORDER,
+ eNoNameRule)
+
+ROLE(GROUPING,
+ "grouping",
+ ATK_ROLE_PANEL,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_GROUPING,
+ ROLE_SYSTEM_GROUPING,
+ eNoNameRule)
+
+ROLE(SEPARATOR,
+ "separator",
+ ATK_ROLE_SEPARATOR,
+ NSAccessibilitySplitterRole,
+ ROLE_SYSTEM_SEPARATOR,
+ ROLE_SYSTEM_SEPARATOR,
+ eNoNameRule)
+
+ROLE(TOOLBAR,
+ "toolbar",
+ ATK_ROLE_TOOL_BAR,
+ NSAccessibilityToolbarRole,
+ ROLE_SYSTEM_TOOLBAR,
+ ROLE_SYSTEM_TOOLBAR,
+ eNoNameRule)
+
+ROLE(STATUSBAR,
+ "statusbar",
+ ATK_ROLE_STATUSBAR,
+ NSAccessibilityUnknownRole, //Doesn't exist on OS X (a status bar is its parts; a progressbar, a label, etc.)
+ ROLE_SYSTEM_STATUSBAR,
+ ROLE_SYSTEM_STATUSBAR,
+ eNoNameRule)
+
+ROLE(TABLE,
+ "table",
+ ATK_ROLE_TABLE,
+ NSAccessibilityTableRole,
+ ROLE_SYSTEM_TABLE,
+ ROLE_SYSTEM_TABLE,
+ eNoNameRule)
+
+ROLE(COLUMNHEADER,
+ "columnheader",
+ ATK_ROLE_COLUMN_HEADER,
+ NSAccessibilityCellRole,
+ ROLE_SYSTEM_COLUMNHEADER,
+ ROLE_SYSTEM_COLUMNHEADER,
+ eNameFromSubtreeRule)
+
+ROLE(ROWHEADER,
+ "rowheader",
+ ATK_ROLE_ROW_HEADER,
+ NSAccessibilityCellRole,
+ ROLE_SYSTEM_ROWHEADER,
+ ROLE_SYSTEM_ROWHEADER,
+ eNameFromSubtreeRule)
+
+ROLE(COLUMN,
+ "column",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityColumnRole,
+ ROLE_SYSTEM_COLUMN,
+ ROLE_SYSTEM_COLUMN,
+ eNameFromSubtreeRule)
+
+ROLE(ROW,
+ "row",
+ ATK_ROLE_TABLE_ROW,
+ NSAccessibilityRowRole,
+ ROLE_SYSTEM_ROW,
+ ROLE_SYSTEM_ROW,
+ eNameFromSubtreeRule)
+
+ROLE(CELL,
+ "cell",
+ ATK_ROLE_TABLE_CELL,
+ NSAccessibilityCellRole,
+ ROLE_SYSTEM_CELL,
+ ROLE_SYSTEM_CELL,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(LINK,
+ "link",
+ ATK_ROLE_LINK,
+ @"AXLink", //10.4+ the attr first define in SDK 10.4, so we define it here too. ROLE_LINK
+ ROLE_SYSTEM_LINK,
+ ROLE_SYSTEM_LINK,
+ eNameFromSubtreeRule)
+
+ROLE(HELPBALLOON,
+ "helpballoon",
+ ATK_ROLE_UNKNOWN,
+ @"AXHelpTag",
+ ROLE_SYSTEM_HELPBALLOON,
+ ROLE_SYSTEM_HELPBALLOON,
+ eNameFromSubtreeRule)
+
+ROLE(CHARACTER,
+ "character",
+ ATK_ROLE_IMAGE,
+ NSAccessibilityUnknownRole, //Unused on OS X.
+ ROLE_SYSTEM_CHARACTER,
+ ROLE_SYSTEM_CHARACTER,
+ eNoNameRule)
+
+ROLE(LIST,
+ "list",
+ ATK_ROLE_LIST,
+ NSAccessibilityListRole,
+ ROLE_SYSTEM_LIST,
+ ROLE_SYSTEM_LIST,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(LISTITEM,
+ "listitem",
+ ATK_ROLE_LIST_ITEM,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_LISTITEM,
+ ROLE_SYSTEM_LISTITEM,
+ eNameFromSubtreeRule)
+
+ROLE(OUTLINE,
+ "outline",
+ ATK_ROLE_TREE,
+ NSAccessibilityOutlineRole,
+ ROLE_SYSTEM_OUTLINE,
+ ROLE_SYSTEM_OUTLINE,
+ eNoNameRule)
+
+ROLE(OUTLINEITEM,
+ "outlineitem",
+ ATK_ROLE_LIST_ITEM,
+ NSAccessibilityRowRole, //XXX: use OutlineRow as subrole.
+ ROLE_SYSTEM_OUTLINEITEM,
+ ROLE_SYSTEM_OUTLINEITEM,
+ eNameFromSubtreeRule)
+
+ROLE(PAGETAB,
+ "pagetab",
+ ATK_ROLE_PAGE_TAB,
+ NSAccessibilityRadioButtonRole,
+ ROLE_SYSTEM_PAGETAB,
+ ROLE_SYSTEM_PAGETAB,
+ eNameFromSubtreeRule)
+
+ROLE(PROPERTYPAGE,
+ "propertypage",
+ ATK_ROLE_SCROLL_PANE,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_PROPERTYPAGE,
+ ROLE_SYSTEM_PROPERTYPAGE,
+ eNoNameRule)
+
+ROLE(INDICATOR,
+ "indicator",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_INDICATOR,
+ ROLE_SYSTEM_INDICATOR,
+ eNoNameRule)
+
+ROLE(GRAPHIC,
+ "graphic",
+ ATK_ROLE_IMAGE,
+ NSAccessibilityImageRole,
+ ROLE_SYSTEM_GRAPHIC,
+ ROLE_SYSTEM_GRAPHIC,
+ eNoNameRule)
+
+ROLE(STATICTEXT,
+ "statictext",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityStaticTextRole,
+ ROLE_SYSTEM_STATICTEXT,
+ ROLE_SYSTEM_STATICTEXT,
+ eNoNameRule)
+
+ROLE(TEXT_LEAF,
+ "text leaf",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityStaticTextRole,
+ ROLE_SYSTEM_TEXT,
+ ROLE_SYSTEM_TEXT,
+ eNoNameRule)
+
+ROLE(PUSHBUTTON,
+ "pushbutton",
+ ATK_ROLE_PUSH_BUTTON,
+ NSAccessibilityButtonRole,
+ ROLE_SYSTEM_PUSHBUTTON,
+ ROLE_SYSTEM_PUSHBUTTON,
+ eNameFromSubtreeRule)
+
+ROLE(CHECKBUTTON,
+ "checkbutton",
+ ATK_ROLE_CHECK_BOX,
+ NSAccessibilityCheckBoxRole,
+ ROLE_SYSTEM_CHECKBUTTON,
+ ROLE_SYSTEM_CHECKBUTTON,
+ eNameFromSubtreeRule)
+
+ROLE(RADIOBUTTON,
+ "radiobutton",
+ ATK_ROLE_RADIO_BUTTON,
+ NSAccessibilityRadioButtonRole,
+ ROLE_SYSTEM_RADIOBUTTON,
+ ROLE_SYSTEM_RADIOBUTTON,
+ eNameFromSubtreeRule)
+
+ROLE(COMBOBOX,
+ "combobox",
+ ATK_ROLE_COMBO_BOX,
+ NSAccessibilityPopUpButtonRole,
+ ROLE_SYSTEM_COMBOBOX,
+ ROLE_SYSTEM_COMBOBOX,
+ eNameFromValueRule)
+
+ROLE(DROPLIST,
+ "droplist",
+ ATK_ROLE_COMBO_BOX,
+ NSAccessibilityPopUpButtonRole,
+ ROLE_SYSTEM_DROPLIST,
+ ROLE_SYSTEM_DROPLIST,
+ eNoNameRule)
+
+ROLE(PROGRESSBAR,
+ "progressbar",
+ ATK_ROLE_PROGRESS_BAR,
+ NSAccessibilityProgressIndicatorRole,
+ ROLE_SYSTEM_PROGRESSBAR,
+ ROLE_SYSTEM_PROGRESSBAR,
+ eNameFromValueRule)
+
+ROLE(DIAL,
+ "dial",
+ ATK_ROLE_DIAL,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_DIAL,
+ ROLE_SYSTEM_DIAL,
+ eNoNameRule)
+
+ROLE(HOTKEYFIELD,
+ "hotkeyfield",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_HOTKEYFIELD,
+ ROLE_SYSTEM_HOTKEYFIELD,
+ eNoNameRule)
+
+ROLE(SLIDER,
+ "slider",
+ ATK_ROLE_SLIDER,
+ NSAccessibilitySliderRole,
+ ROLE_SYSTEM_SLIDER,
+ ROLE_SYSTEM_SLIDER,
+ eNameFromValueRule)
+
+ROLE(SPINBUTTON,
+ "spinbutton",
+ ATK_ROLE_SPIN_BUTTON,
+ NSAccessibilityIncrementorRole, //Subroles: Increment/Decrement.
+ ROLE_SYSTEM_SPINBUTTON,
+ ROLE_SYSTEM_SPINBUTTON,
+ eNameFromValueRule)
+
+ROLE(DIAGRAM,
+ "diagram",
+ ATK_ROLE_IMAGE,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_DIAGRAM,
+ ROLE_SYSTEM_DIAGRAM,
+ eNoNameRule)
+
+ROLE(ANIMATION,
+ "animation",
+ ATK_ROLE_ANIMATION,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_ANIMATION,
+ ROLE_SYSTEM_ANIMATION,
+ eNoNameRule)
+
+ROLE(EQUATION,
+ "equation",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_EQUATION,
+ ROLE_SYSTEM_EQUATION,
+ eNoNameRule)
+
+ROLE(BUTTONDROPDOWN,
+ "buttondropdown",
+ ATK_ROLE_PUSH_BUTTON,
+ NSAccessibilityPopUpButtonRole,
+ ROLE_SYSTEM_BUTTONDROPDOWN,
+ ROLE_SYSTEM_BUTTONDROPDOWN,
+ eNameFromSubtreeRule)
+
+ROLE(BUTTONMENU,
+ "buttonmenu",
+ ATK_ROLE_PUSH_BUTTON,
+ NSAccessibilityMenuButtonRole,
+ ROLE_SYSTEM_BUTTONMENU,
+ ROLE_SYSTEM_BUTTONMENU,
+ eNameFromSubtreeRule)
+
+ROLE(BUTTONDROPDOWNGRID,
+ "buttondropdowngrid",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_BUTTONDROPDOWNGRID,
+ ROLE_SYSTEM_BUTTONDROPDOWNGRID,
+ eNameFromSubtreeRule)
+
+ROLE(WHITESPACE,
+ "whitespace",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_WHITESPACE,
+ ROLE_SYSTEM_WHITESPACE,
+ eNoNameRule)
+
+ROLE(PAGETABLIST,
+ "pagetablist",
+ ATK_ROLE_PAGE_TAB_LIST,
+ NSAccessibilityTabGroupRole,
+ ROLE_SYSTEM_PAGETABLIST,
+ ROLE_SYSTEM_PAGETABLIST,
+ eNoNameRule)
+
+ROLE(CLOCK,
+ "clock",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole, //Unused on OS X
+ ROLE_SYSTEM_CLOCK,
+ ROLE_SYSTEM_CLOCK,
+ eNoNameRule)
+
+ROLE(SPLITBUTTON,
+ "splitbutton",
+ ATK_ROLE_PUSH_BUTTON,
+ NSAccessibilityButtonRole,
+ ROLE_SYSTEM_SPLITBUTTON,
+ ROLE_SYSTEM_SPLITBUTTON,
+ eNoNameRule)
+
+ROLE(IPADDRESS,
+ "ipaddress",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_IPADDRESS,
+ ROLE_SYSTEM_IPADDRESS,
+ eNoNameRule)
+
+ROLE(ACCEL_LABEL,
+ "accel label",
+ ATK_ROLE_ACCEL_LABEL,
+ NSAccessibilityStaticTextRole,
+ ROLE_SYSTEM_STATICTEXT,
+ ROLE_SYSTEM_STATICTEXT,
+ eNoNameRule)
+
+ROLE(ARROW,
+ "arrow",
+ ATK_ROLE_ARROW,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_INDICATOR,
+ ROLE_SYSTEM_INDICATOR,
+ eNoNameRule)
+
+ROLE(CANVAS,
+ "canvas",
+ ATK_ROLE_CANVAS,
+ NSAccessibilityImageRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_CANVAS,
+ eNoNameRule)
+
+ROLE(CHECK_MENU_ITEM,
+ "check menu item",
+ ATK_ROLE_CHECK_MENU_ITEM,
+ NSAccessibilityMenuItemRole,
+ ROLE_SYSTEM_MENUITEM,
+ IA2_ROLE_CHECK_MENU_ITEM,
+ eNameFromSubtreeRule)
+
+ROLE(COLOR_CHOOSER,
+ "color chooser",
+ ATK_ROLE_COLOR_CHOOSER,
+ NSAccessibilityColorWellRole,
+ ROLE_SYSTEM_DIALOG,
+ IA2_ROLE_COLOR_CHOOSER,
+ eNoNameRule)
+
+ROLE(DATE_EDITOR,
+ "date editor",
+ ATK_ROLE_DATE_EDITOR,
+ NSAccessibilityUnknownRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_DATE_EDITOR,
+ eNoNameRule)
+
+ROLE(DESKTOP_ICON,
+ "desktop icon",
+ ATK_ROLE_DESKTOP_ICON,
+ NSAccessibilityImageRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_DESKTOP_ICON,
+ eNoNameRule)
+
+ROLE(DESKTOP_FRAME,
+ "desktop frame",
+ ATK_ROLE_DESKTOP_FRAME,
+ NSAccessibilityUnknownRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_DESKTOP_PANE,
+ eNoNameRule)
+
+ROLE(DIRECTORY_PANE,
+ "directory pane",
+ ATK_ROLE_DIRECTORY_PANE,
+ NSAccessibilityBrowserRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_DIRECTORY_PANE,
+ eNoNameRule)
+
+ROLE(FILE_CHOOSER,
+ "file chooser",
+ ATK_ROLE_FILE_CHOOSER,
+ NSAccessibilityUnknownRole, //Unused on OS X
+ USE_ROLE_STRING,
+ IA2_ROLE_FILE_CHOOSER,
+ eNoNameRule)
+
+ROLE(FONT_CHOOSER,
+ "font chooser",
+ ATK_ROLE_FONT_CHOOSER,
+ NSAccessibilityUnknownRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_FONT_CHOOSER,
+ eNoNameRule)
+
+ROLE(CHROME_WINDOW,
+ "chrome window",
+ ATK_ROLE_FRAME,
+ NSAccessibilityGroupRole, //Contains the main Firefox UI
+ ROLE_SYSTEM_APPLICATION,
+ IA2_ROLE_FRAME,
+ eNoNameRule)
+
+ROLE(GLASS_PANE,
+ "glass pane",
+ ATK_ROLE_GLASS_PANE,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_GLASS_PANE,
+ eNoNameRule)
+
+ROLE(HTML_CONTAINER,
+ "html container",
+ ATK_ROLE_HTML_CONTAINER,
+ NSAccessibilityUnknownRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_UNKNOWN,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(ICON,
+ "icon",
+ ATK_ROLE_ICON,
+ NSAccessibilityImageRole,
+ ROLE_SYSTEM_PUSHBUTTON,
+ IA2_ROLE_ICON,
+ eNoNameRule)
+
+ROLE(LABEL,
+ "label",
+ ATK_ROLE_LABEL,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_STATICTEXT,
+ IA2_ROLE_LABEL,
+ eNameFromSubtreeRule)
+
+ROLE(LAYERED_PANE,
+ "layered pane",
+ ATK_ROLE_LAYERED_PANE,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_LAYERED_PANE,
+ eNoNameRule)
+
+ROLE(OPTION_PANE,
+ "option pane",
+ ATK_ROLE_OPTION_PANE,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_OPTION_PANE,
+ eNoNameRule)
+
+ROLE(PASSWORD_TEXT,
+ "password text",
+ ATK_ROLE_PASSWORD_TEXT,
+ NSAccessibilityTextFieldRole,
+ ROLE_SYSTEM_TEXT,
+ ROLE_SYSTEM_TEXT,
+ eNoNameRule)
+
+ROLE(POPUP_MENU,
+ "popup menu",
+ ATK_ROLE_POPUP_MENU,
+ NSAccessibilityUnknownRole, //Unused
+ ROLE_SYSTEM_MENUPOPUP,
+ ROLE_SYSTEM_MENUPOPUP,
+ eNoNameRule)
+
+ROLE(RADIO_MENU_ITEM,
+ "radio menu item",
+ ATK_ROLE_RADIO_MENU_ITEM,
+ NSAccessibilityMenuItemRole,
+ ROLE_SYSTEM_MENUITEM,
+ IA2_ROLE_RADIO_MENU_ITEM,
+ eNameFromSubtreeRule)
+
+ROLE(ROOT_PANE,
+ "root pane",
+ ATK_ROLE_ROOT_PANE,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_ROOT_PANE,
+ eNoNameRule)
+
+ROLE(SCROLL_PANE,
+ "scroll pane",
+ ATK_ROLE_SCROLL_PANE,
+ NSAccessibilityScrollAreaRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_SCROLL_PANE,
+ eNoNameRule)
+
+ROLE(SPLIT_PANE,
+ "split pane",
+ ATK_ROLE_SPLIT_PANE,
+ NSAccessibilitySplitGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_SPLIT_PANE,
+ eNoNameRule)
+
+ROLE(TABLE_COLUMN_HEADER,
+ "table column header",
+ ATK_ROLE_TABLE_COLUMN_HEADER,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_COLUMNHEADER,
+ ROLE_SYSTEM_COLUMNHEADER,
+ eNameFromSubtreeRule)
+
+ROLE(TABLE_ROW_HEADER,
+ "table row header",
+ ATK_ROLE_TABLE_ROW_HEADER,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_ROWHEADER,
+ ROLE_SYSTEM_ROWHEADER,
+ eNameFromSubtreeRule)
+
+ROLE(TEAR_OFF_MENU_ITEM,
+ "tear off menu item",
+ ATK_ROLE_TEAR_OFF_MENU_ITEM,
+ NSAccessibilityMenuItemRole,
+ ROLE_SYSTEM_MENUITEM,
+ IA2_ROLE_TEAR_OFF_MENU,
+ eNameFromSubtreeRule)
+
+ROLE(TERMINAL,
+ "terminal",
+ ATK_ROLE_TERMINAL,
+ NSAccessibilityUnknownRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_TERMINAL,
+ eNoNameRule)
+
+ROLE(TEXT_CONTAINER,
+ "text container",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_TEXT_FRAME,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(TOGGLE_BUTTON,
+ "toggle button",
+ ATK_ROLE_TOGGLE_BUTTON,
+ NSAccessibilityButtonRole,
+ ROLE_SYSTEM_PUSHBUTTON,
+ IA2_ROLE_TOGGLE_BUTTON,
+ eNameFromSubtreeRule)
+
+ROLE(TREE_TABLE,
+ "tree table",
+ ATK_ROLE_TREE_TABLE,
+ NSAccessibilityTableRole,
+ ROLE_SYSTEM_OUTLINE,
+ ROLE_SYSTEM_OUTLINE,
+ eNoNameRule)
+
+ROLE(VIEWPORT,
+ "viewport",
+ ATK_ROLE_VIEWPORT,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_PANE,
+ IA2_ROLE_VIEW_PORT,
+ eNoNameRule)
+
+ROLE(HEADER,
+ "header",
+ ATK_ROLE_HEADER,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_HEADER,
+ eNoNameRule)
+
+ROLE(FOOTER,
+ "footer",
+ ATK_ROLE_FOOTER,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_FOOTER,
+ eNoNameRule)
+
+ROLE(PARAGRAPH,
+ "paragraph",
+ ATK_ROLE_PARAGRAPH,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_PARAGRAPH,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(RULER,
+ "ruler",
+ ATK_ROLE_RULER,
+ @"AXRuler", //10.4+ only, so we re-define the constant.
+ USE_ROLE_STRING,
+ IA2_ROLE_RULER,
+ eNoNameRule)
+
+ROLE(AUTOCOMPLETE,
+ "autocomplete",
+ ATK_ROLE_AUTOCOMPLETE,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_COMBOBOX,
+ ROLE_SYSTEM_COMBOBOX,
+ eNoNameRule)
+
+ROLE(EDITBAR,
+ "editbar",
+ ATK_ROLE_EDITBAR,
+ NSAccessibilityTextFieldRole,
+ ROLE_SYSTEM_TEXT,
+ IA2_ROLE_EDITBAR,
+ eNoNameRule)
+
+ROLE(ENTRY,
+ "entry",
+ ATK_ROLE_ENTRY,
+ NSAccessibilityTextFieldRole,
+ ROLE_SYSTEM_TEXT,
+ ROLE_SYSTEM_TEXT,
+ eNameFromValueRule)
+
+ROLE(CAPTION,
+ "caption",
+ ATK_ROLE_CAPTION,
+ NSAccessibilityStaticTextRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_CAPTION,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(DOCUMENT_FRAME,
+ "document frame",
+ ATK_ROLE_DOCUMENT_FRAME,
+ NSAccessibilityScrollAreaRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(HEADING,
+ "heading",
+ ATK_ROLE_HEADING,
+ @"AXHeading",
+ USE_ROLE_STRING,
+ IA2_ROLE_HEADING,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(PAGE,
+ "page",
+ ATK_ROLE_PAGE,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_PAGE,
+ eNoNameRule)
+
+ROLE(SECTION,
+ "section",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_SECTION,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(REDUNDANT_OBJECT,
+ "redundant object",
+ ATK_ROLE_REDUNDANT_OBJECT,
+ NSAccessibilityUnknownRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_REDUNDANT_OBJECT,
+ eNoNameRule)
+
+ROLE(FORM,
+ "form",
+ ATK_ROLE_FORM,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_FORM,
+ eNoNameRule)
+
+ROLE(IME,
+ "ime",
+ ATK_ROLE_INPUT_METHOD_WINDOW,
+ NSAccessibilityUnknownRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_INPUT_METHOD_WINDOW,
+ eNoNameRule)
+
+ROLE(APP_ROOT,
+ "app root",
+ ATK_ROLE_APPLICATION,
+ NSAccessibilityUnknownRole, //Unused on OS X
+ ROLE_SYSTEM_APPLICATION,
+ ROLE_SYSTEM_APPLICATION,
+ eNoNameRule)
+
+ROLE(PARENT_MENUITEM,
+ "parent menuitem",
+ ATK_ROLE_MENU,
+ NSAccessibilityMenuItemRole,
+ ROLE_SYSTEM_MENUITEM,
+ ROLE_SYSTEM_MENUITEM,
+ eNameFromSubtreeRule)
+
+ROLE(CALENDAR,
+ "calendar",
+ ATK_ROLE_CALENDAR,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_CLIENT,
+ ROLE_SYSTEM_CLIENT,
+ eNoNameRule)
+
+ROLE(COMBOBOX_LIST,
+ "combobox list",
+ ATK_ROLE_MENU,
+ NSAccessibilityMenuRole,
+ ROLE_SYSTEM_LIST,
+ ROLE_SYSTEM_LIST,
+ eNoNameRule)
+
+ROLE(COMBOBOX_OPTION,
+ "combobox option",
+ ATK_ROLE_MENU_ITEM,
+ NSAccessibilityMenuItemRole,
+ ROLE_SYSTEM_LISTITEM,
+ ROLE_SYSTEM_LISTITEM,
+ eNameFromSubtreeRule)
+
+ROLE(IMAGE_MAP,
+ "image map",
+ ATK_ROLE_IMAGE,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_GRAPHIC,
+ ROLE_SYSTEM_GRAPHIC,
+ eNoNameRule)
+
+ROLE(OPTION,
+ "listbox option",
+ ATK_ROLE_LIST_ITEM,
+ NSAccessibilityStaticTextRole,
+ ROLE_SYSTEM_LISTITEM,
+ ROLE_SYSTEM_LISTITEM,
+ eNameFromSubtreeRule)
+
+ROLE(RICH_OPTION,
+ "listbox rich option",
+ ATK_ROLE_LIST_ITEM,
+ NSAccessibilityRowRole,
+ ROLE_SYSTEM_LISTITEM,
+ ROLE_SYSTEM_LISTITEM,
+ eNameFromSubtreeRule)
+
+ROLE(LISTBOX,
+ "listbox",
+ ATK_ROLE_LIST_BOX,
+ NSAccessibilityListRole,
+ ROLE_SYSTEM_LIST,
+ ROLE_SYSTEM_LIST,
+ eNoNameRule)
+
+ROLE(FLAT_EQUATION,
+ "flat equation",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityUnknownRole,
+ ROLE_SYSTEM_EQUATION,
+ ROLE_SYSTEM_EQUATION,
+ eNoNameRule)
+
+ROLE(GRID_CELL,
+ "gridcell",
+ ATK_ROLE_TABLE_CELL,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_CELL,
+ ROLE_SYSTEM_CELL,
+ eNameFromSubtreeRule)
+
+ROLE(EMBEDDED_OBJECT,
+ "embedded object",
+ ATK_ROLE_PANEL,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_EMBEDDED_OBJECT,
+ eNoNameRule)
+
+ROLE(NOTE,
+ "note",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_NOTE,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(FIGURE,
+ "figure",
+ ATK_ROLE_PANEL,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_GROUPING,
+ ROLE_SYSTEM_GROUPING,
+ eNoNameRule)
+
+ROLE(CHECK_RICH_OPTION,
+ "check rich option",
+ ATK_ROLE_CHECK_BOX,
+ NSAccessibilityCheckBoxRole,
+ ROLE_SYSTEM_CHECKBUTTON,
+ ROLE_SYSTEM_CHECKBUTTON,
+ eNameFromSubtreeRule)
+
+ROLE(DEFINITION_LIST,
+ "definitionlist",
+ ATK_ROLE_LIST,
+ NSAccessibilityListRole,
+ ROLE_SYSTEM_LIST,
+ ROLE_SYSTEM_LIST,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(TERM,
+ "term",
+ ATK_ROLE_LIST_ITEM,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_LISTITEM,
+ ROLE_SYSTEM_LISTITEM,
+ eNameFromSubtreeRule)
+
+ROLE(DEFINITION,
+ "definition",
+ ATK_ROLE_PARAGRAPH,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_PARAGRAPH,
+ eNameFromSubtreeRule)
+
+ROLE(KEY,
+ "key",
+ ATK_ROLE_PUSH_BUTTON,
+ NSAccessibilityButtonRole,
+ ROLE_SYSTEM_PUSHBUTTON,
+ ROLE_SYSTEM_PUSHBUTTON,
+ eNameFromSubtreeRule)
+
+ROLE(SWITCH,
+ "switch",
+ ATK_ROLE_TOGGLE_BUTTON,
+ NSAccessibilityCheckBoxRole,
+ ROLE_SYSTEM_CHECKBUTTON,
+ IA2_ROLE_TOGGLE_BUTTON,
+ eNameFromSubtreeRule)
+
+ROLE(MATHML_MATH,
+ "math",
+ ATK_ROLE_MATH,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_EQUATION,
+ ROLE_SYSTEM_EQUATION,
+ eNoNameRule)
+
+ROLE(MATHML_IDENTIFIER,
+ "mathml identifier",
+ ATK_ROLE_STATIC,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNameFromSubtreeRule)
+
+ROLE(MATHML_NUMBER,
+ "mathml number",
+ ATK_ROLE_STATIC,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNameFromSubtreeRule)
+
+ROLE(MATHML_OPERATOR,
+ "mathml operator",
+ ATK_ROLE_STATIC,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNameFromSubtreeRule)
+
+ROLE(MATHML_TEXT,
+ "mathml text",
+ ATK_ROLE_STATIC,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNameFromSubtreeRule)
+
+ROLE(MATHML_STRING_LITERAL,
+ "mathml string literal",
+ ATK_ROLE_STATIC,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNameFromSubtreeRule)
+
+ROLE(MATHML_GLYPH,
+ "mathml glyph",
+ ATK_ROLE_IMAGE,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNameFromSubtreeRule)
+
+ROLE(MATHML_ROW,
+ "mathml row",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_FRACTION,
+ "mathml fraction",
+ ATK_ROLE_MATH_FRACTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_SQUARE_ROOT,
+ "mathml square root",
+ ATK_ROLE_MATH_ROOT,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_ROOT,
+ "mathml root",
+ ATK_ROLE_MATH_ROOT,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_FENCED,
+ "mathml fenced",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_ENCLOSED,
+ "mathml enclosed",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_STYLE,
+ "mathml style",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_SUB,
+ "mathml sub",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_SUP,
+ "mathml sup",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_SUB_SUP,
+ "mathml sub sup",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_UNDER,
+ "mathml under",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_OVER,
+ "mathml over",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_UNDER_OVER,
+ "mathml under over",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_MULTISCRIPTS,
+ "mathml multiscripts",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_TABLE,
+ "mathml table",
+ ATK_ROLE_TABLE,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_LABELED_ROW,
+ "mathml labeled row",
+ ATK_ROLE_TABLE_ROW,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_TABLE_ROW,
+ "mathml table row",
+ ATK_ROLE_TABLE_ROW,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_CELL,
+ "mathml cell",
+ ATK_ROLE_TABLE_CELL,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_ACTION,
+ "mathml action",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_ERROR,
+ "mathml error",
+ ATK_ROLE_SECTION,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_STACK,
+ "mathml stack",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_LONG_DIVISION,
+ "mathml long division",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_STACK_GROUP,
+ "mathml stack group",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_STACK_ROW,
+ "mathml stack row",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_STACK_CARRIES,
+ "mathml stack carries",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_STACK_CARRY,
+ "mathml stack carry",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(MATHML_STACK_LINE,
+ "mathml stack line",
+ ATK_ROLE_UNKNOWN,
+ NSAccessibilityGroupRole,
+ 0,
+ IA2_ROLE_UNKNOWN,
+ eNoNameRule)
+
+ROLE(RADIO_GROUP,
+ "grouping",
+ ATK_ROLE_PANEL,
+ NSAccessibilityRadioGroupRole,
+ ROLE_SYSTEM_GROUPING,
+ ROLE_SYSTEM_GROUPING,
+ eNoNameRule)
+
+ROLE(TEXT,
+ "text",
+ ATK_ROLE_STATIC,
+ NSAccessibilityGroupRole,
+ USE_ROLE_STRING,
+ IA2_ROLE_TEXT_FRAME,
+ eNameFromSubtreeIfReqRule)
+
+ROLE(DETAILS,
+ "details",
+ ATK_ROLE_PANEL,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_GROUPING,
+ ROLE_SYSTEM_GROUPING,
+ eNoNameRule)
+
+ROLE(SUMMARY,
+ "summary",
+ ATK_ROLE_PUSH_BUTTON,
+ NSAccessibilityGroupRole,
+ ROLE_SYSTEM_PUSHBUTTON,
+ ROLE_SYSTEM_PUSHBUTTON,
+ eNameFromSubtreeRule)
+
diff --git a/accessible/base/SelectionManager.cpp b/accessible/base/SelectionManager.cpp
new file mode 100644
index 000000000..30e01cbfc
--- /dev/null
+++ b/accessible/base/SelectionManager.cpp
@@ -0,0 +1,232 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "mozilla/a11y/SelectionManager.h"
+
+#include "DocAccessible-inl.h"
+#include "HyperTextAccessible.h"
+#include "HyperTextAccessible-inl.h"
+#include "nsAccessibilityService.h"
+#include "nsAccUtils.h"
+#include "nsCoreUtils.h"
+#include "nsEventShell.h"
+#include "nsFrameSelection.h"
+
+#include "nsIAccessibleTypes.h"
+#include "nsIDOMDocument.h"
+#include "nsIPresShell.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+using mozilla::dom::Selection;
+
+struct mozilla::a11y::SelData final
+{
+ SelData(Selection* aSel, int32_t aReason) :
+ mSel(aSel), mReason(aReason) {}
+
+ RefPtr<Selection> mSel;
+ int16_t mReason;
+
+ NS_INLINE_DECL_REFCOUNTING(SelData)
+
+private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~SelData() {}
+};
+
+SelectionManager::SelectionManager() :
+ mCaretOffset(-1), mAccWithCaret(nullptr)
+{
+
+}
+
+void
+SelectionManager::ClearControlSelectionListener()
+{
+
+ // Remove 'this' registered as selection listener for the normal selection.
+ nsCOMPtr<nsISelection> normalSel = do_QueryReferent(mCurrCtrlNormalSel);
+ if (normalSel) {
+ normalSel->AsSelection()->RemoveSelectionListener(this);
+ mCurrCtrlNormalSel = nullptr;
+ }
+
+ // Remove 'this' registered as selection listener for the spellcheck
+ // selection.
+ nsCOMPtr<nsISelection> spellSel = do_QueryReferent(mCurrCtrlSpellSel);
+ if (spellSel) {
+ spellSel->AsSelection()->RemoveSelectionListener(this);
+ mCurrCtrlSpellSel = nullptr;
+ }
+}
+
+void
+SelectionManager::SetControlSelectionListener(dom::Element* aFocusedElm)
+{
+ // When focus moves such that the caret is part of a new frame selection
+ // this removes the old selection listener and attaches a new one for
+ // the current focus.
+ ClearControlSelectionListener();
+
+ nsIFrame* controlFrame = aFocusedElm->GetPrimaryFrame();
+ if (!controlFrame)
+ return;
+
+ const nsFrameSelection* frameSel = controlFrame->GetConstFrameSelection();
+ NS_ASSERTION(frameSel, "No frame selection for focused element!");
+ if (!frameSel)
+ return;
+
+ // Register 'this' as selection listener for the normal selection.
+ nsCOMPtr<nsISelection> normalSel = frameSel->GetSelection(SelectionType::eNormal);
+ normalSel->AsSelection()->AddSelectionListener(this);
+ mCurrCtrlNormalSel = do_GetWeakReference(normalSel);
+
+ // Register 'this' as selection listener for the spell check selection.
+ nsCOMPtr<nsISelection> spellSel = frameSel->GetSelection(SelectionType::eSpellCheck);
+ spellSel->AsSelection()->AddSelectionListener(this);
+ mCurrCtrlSpellSel = do_GetWeakReference(spellSel);
+}
+
+void
+SelectionManager::AddDocSelectionListener(nsIPresShell* aPresShell)
+{
+ const nsFrameSelection* frameSel = aPresShell->ConstFrameSelection();
+
+ // Register 'this' as selection listener for the normal selection.
+ Selection* normalSel = frameSel->GetSelection(SelectionType::eNormal);
+ normalSel->AddSelectionListener(this);
+
+ // Register 'this' as selection listener for the spell check selection.
+ Selection* spellSel = frameSel->GetSelection(SelectionType::eSpellCheck);
+ spellSel->AddSelectionListener(this);
+}
+
+void
+SelectionManager::RemoveDocSelectionListener(nsIPresShell* aPresShell)
+{
+ const nsFrameSelection* frameSel = aPresShell->ConstFrameSelection();
+
+ // Remove 'this' registered as selection listener for the normal selection.
+ Selection* normalSel = frameSel->GetSelection(SelectionType::eNormal);
+ normalSel->RemoveSelectionListener(this);
+
+ // Remove 'this' registered as selection listener for the spellcheck
+ // selection.
+ Selection* spellSel = frameSel->GetSelection(SelectionType::eSpellCheck);
+ spellSel->RemoveSelectionListener(this);
+}
+
+void
+SelectionManager::ProcessTextSelChangeEvent(AccEvent* aEvent)
+{
+ // Fire selection change event if it's not pure caret-move selection change,
+ // i.e. the accessible has or had not collapsed selection.
+ AccTextSelChangeEvent* event = downcast_accEvent(aEvent);
+ if (!event->IsCaretMoveOnly())
+ nsEventShell::FireEvent(aEvent);
+
+ // Fire caret move event if there's a caret in the selection.
+ nsINode* caretCntrNode =
+ nsCoreUtils::GetDOMNodeFromDOMPoint(event->mSel->GetFocusNode(),
+ event->mSel->FocusOffset());
+ if (!caretCntrNode)
+ return;
+
+ HyperTextAccessible* caretCntr = nsAccUtils::GetTextContainer(caretCntrNode);
+ NS_ASSERTION(caretCntr,
+ "No text container for focus while there's one for common ancestor?!");
+ if (!caretCntr)
+ return;
+
+ Selection* selection = caretCntr->DOMSelection();
+
+ // XXX Sometimes we can't get a selection for caretCntr, in that case assume
+ // event->mSel is correct.
+ if (!selection)
+ selection = event->mSel;
+
+ mCaretOffset = caretCntr->DOMPointToOffset(selection->GetFocusNode(),
+ selection->FocusOffset());
+ mAccWithCaret = caretCntr;
+ if (mCaretOffset != -1) {
+ RefPtr<AccCaretMoveEvent> caretMoveEvent =
+ new AccCaretMoveEvent(caretCntr, mCaretOffset, aEvent->FromUserInput());
+ nsEventShell::FireEvent(caretMoveEvent);
+ }
+}
+
+NS_IMETHODIMP
+SelectionManager::NotifySelectionChanged(nsIDOMDocument* aDOMDocument,
+ nsISelection* aSelection,
+ int16_t aReason)
+{
+ if (NS_WARN_IF(!aDOMDocument) || NS_WARN_IF(!aSelection)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsIDocument> documentNode(do_QueryInterface(aDOMDocument));
+ DocAccessible* document = GetAccService()->GetDocAccessible(documentNode);
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eSelection))
+ logging::SelChange(aSelection, document, aReason);
+#endif
+
+ if (document) {
+ // Selection manager has longer lifetime than any document accessible,
+ // so that we are guaranteed that the notification is processed before
+ // the selection manager is destroyed.
+ RefPtr<SelData> selData =
+ new SelData(aSelection->AsSelection(), aReason);
+ document->HandleNotification<SelectionManager, SelData>
+ (this, &SelectionManager::ProcessSelectionChanged, selData);
+ }
+
+ return NS_OK;
+}
+
+void
+SelectionManager::ProcessSelectionChanged(SelData* aSelData)
+{
+ Selection* selection = aSelData->mSel;
+ if (!selection->GetPresShell())
+ return;
+
+ const nsRange* range = selection->GetAnchorFocusRange();
+ nsINode* cntrNode = nullptr;
+ if (range)
+ cntrNode = range->GetCommonAncestor();
+
+ if (!cntrNode) {
+ cntrNode = selection->GetFrameSelection()->GetAncestorLimiter();
+ if (!cntrNode) {
+ cntrNode = selection->GetPresShell()->GetDocument();
+ NS_ASSERTION(aSelData->mSel->GetPresShell()->ConstFrameSelection() == selection->GetFrameSelection(),
+ "Wrong selection container was used!");
+ }
+ }
+
+ HyperTextAccessible* text = nsAccUtils::GetTextContainer(cntrNode);
+ if (!text) {
+ NS_NOTREACHED("We must reach document accessible implementing text interface!");
+ return;
+ }
+
+ if (selection->GetType() == SelectionType::eNormal) {
+ RefPtr<AccEvent> event =
+ new AccTextSelChangeEvent(text, selection, aSelData->mReason);
+ text->Document()->FireDelayedEvent(event);
+
+ } else if (selection->GetType() == SelectionType::eSpellCheck) {
+ // XXX: fire an event for container accessible of the focus/anchor range
+ // of the spelcheck selection.
+ text->Document()->FireDelayedEvent(nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED,
+ text);
+ }
+}
diff --git a/accessible/base/SelectionManager.h b/accessible/base/SelectionManager.h
new file mode 100644
index 000000000..d8e6dabbd
--- /dev/null
+++ b/accessible/base/SelectionManager.h
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_SelectionManager_h__
+#define mozilla_a11y_SelectionManager_h__
+
+#include "nsIFrame.h"
+#include "nsISelectionListener.h"
+
+class nsIPresShell;
+
+namespace mozilla {
+
+namespace dom {
+class Element;
+}
+
+namespace a11y {
+
+class AccEvent;
+class HyperTextAccessible;
+
+/**
+ * This special accessibility class is for the caret and selection management.
+ * There is only 1 visible caret per top level window. However, there may be
+ * several visible selections.
+ *
+ * The important selections are the one owned by each document, and the one in
+ * the currently focused control.
+ *
+ * On Windows this class is used to move an invisible system caret that
+ * shadows the Mozilla caret. Windows will also automatically map this to
+ * the MSAA caret accessible object (via OBJID_CARET) (as opposed to the root
+ * accessible tree for a window which is retrieved with OBJID_CLIENT).
+ *
+ * For ATK and IAccessible2, this class is used to fire caret move and
+ * selection change events.
+ */
+
+struct SelData;
+
+class SelectionManager : public nsISelectionListener
+{
+public:
+ // nsISupports
+ // implemented by derived nsAccessibilityService
+
+ // nsISelectionListener
+ NS_DECL_NSISELECTIONLISTENER
+
+ // SelectionManager
+ void Shutdown() { ClearControlSelectionListener(); }
+
+ /**
+ * Listen to selection events on the focused control.
+ *
+ * Note: only one control's selection events are listened to at a time. This
+ * will remove the previous control's selection listener.
+ */
+ void SetControlSelectionListener(dom::Element* aFocusedElm);
+
+ /**
+ * Stop listening to selection events on the control.
+ */
+ void ClearControlSelectionListener();
+
+ /**
+ * Listen to selection events on the document.
+ */
+ void AddDocSelectionListener(nsIPresShell* aPresShell);
+
+ /**
+ * Stop listening to selection events for a given document
+ */
+ void RemoveDocSelectionListener(nsIPresShell* aShell);
+
+ /**
+ * Process delayed event, results in caret move and text selection change
+ * events.
+ */
+ void ProcessTextSelChangeEvent(AccEvent* aEvent);
+
+ /**
+ * Gets the current caret offset/hypertext accessible pair. If there is no
+ * current pair, then returns -1 for the offset and a nullptr for the
+ * accessible.
+ */
+ inline HyperTextAccessible* AccessibleWithCaret(int32_t* aCaret)
+ {
+ if (aCaret)
+ *aCaret = mCaretOffset;
+
+ return mAccWithCaret;
+ }
+
+ /**
+ * Update caret offset when it doesn't go through a caret move event.
+ */
+ inline void UpdateCaretOffset(HyperTextAccessible* aItem, int32_t aOffset)
+ {
+ mAccWithCaret = aItem;
+ mCaretOffset = aOffset;
+ }
+
+ inline void ResetCaretOffset()
+ {
+ mCaretOffset = -1;
+ mAccWithCaret = nullptr;
+ }
+
+protected:
+
+ SelectionManager();
+
+ /**
+ * Process DOM selection change. Fire selection and caret move events.
+ */
+ void ProcessSelectionChanged(SelData* aSelData);
+
+private:
+ int32_t mCaretOffset;
+ HyperTextAccessible* mAccWithCaret;
+ // Currently focused controls.
+ nsWeakPtr mCurrCtrlNormalSel;
+ nsWeakPtr mCurrCtrlSpellSel;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/States.h b/accessible/base/States.h
new file mode 100644
index 000000000..3f9f486f6
--- /dev/null
+++ b/accessible/base/States.h
@@ -0,0 +1,285 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set expandtab shiftwidth=2 tabstop=2: */
+/* 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 _states_h_
+#define _states_h_
+
+#include <stdint.h>
+
+namespace mozilla {
+namespace a11y {
+namespace states {
+
+ /**
+ * The object is disabled, opposite to enabled and sensitive.
+ */
+ const uint64_t UNAVAILABLE = ((uint64_t) 0x1) << 0;
+
+ /**
+ * The object is selected.
+ */
+ const uint64_t SELECTED = ((uint64_t) 0x1) << 1;
+
+ /**
+ * The object has the keyboard focus.
+ */
+ const uint64_t FOCUSED = ((uint64_t) 0x1) << 2;
+
+ /**
+ * The object is pressed.
+ */
+ const uint64_t PRESSED = ((uint64_t) 0x1) << 3;
+
+ /**
+ * The checkable object is checked, applied to check box controls,
+ * @see CHECKABLE and MIXED states.
+ */
+ const uint64_t CHECKED = ((uint64_t) 0x1) << 4;
+
+ /**
+ * Indicates that the state of a three-state check box or tool bar button is
+ * undetermined. The check box is neither checked or unchecked, and is
+ * in the third or mixed state.
+ */
+ const uint64_t MIXED = ((uint64_t) 0x1) << 5;
+
+ /**
+ * The object is designated read-only, so it can't be edited.
+ */
+ const uint64_t READONLY = ((uint64_t) 0x1) << 6;
+
+ /**
+ * The object is hot-tracked by the mouse, which means that its appearance
+ * has changed to indicate that the mouse pointer is located over it.
+ *
+ * This is currently unused.
+ */
+ const uint64_t HOTTRACKED = ((uint64_t) 0x1) << 7;
+
+ /**
+ * This object is the default button in a window.
+ */
+ const uint64_t DEFAULT = ((uint64_t) 0x1) << 8;
+
+ /**
+ * The expandable object's children are displayed, the opposite of collapsed,
+ * applied to trees, list and other controls.
+ * @see COLLAPSED state
+ */
+ const uint64_t EXPANDED = ((uint64_t) 0x1) << 9;
+
+ /**
+ * The expandable object's children are not displayed, the opposite of
+ * expanded, applied to tree lists and other controls,
+ * @see EXPANDED state.
+ */
+ const uint64_t COLLAPSED = ((uint64_t) 0x1) << 10;
+
+ /**
+ * The control or document can not accept input at this time.
+ */
+ const uint64_t BUSY = ((uint64_t) 0x1) << 11;
+
+ /**
+ * The object is out of normal flow, may be outside of boundaries of its
+ * parent.
+ */
+ const uint64_t FLOATING = ((uint64_t) 0x1) << 12;
+
+ /**
+ * The object can be checked.
+ */
+ const uint64_t CHECKABLE = ((uint64_t) 0x1) << 13;
+
+ /**
+ * This object is a graphic which is rapidly changing appearance.
+ */
+ const uint64_t ANIMATED = ((uint64_t) 0x1) << 14;
+
+ /**
+ * The object is programmatically hidden.
+ * So user action like scrolling or switching tabs won't make this visible.
+ */
+ const uint64_t INVISIBLE = ((uint64_t) 0x1) << 15;
+
+ /**
+ * The object is scrolled off screen.
+ * User action such as scrolling or changing tab may make the object
+ * visible.
+ */
+ const uint64_t OFFSCREEN = ((uint64_t) 0x1) << 16;
+
+ /**
+ * The object can be resized.
+ */
+ const uint64_t SIZEABLE = ((uint64_t) 0x1) << 17;
+
+ /**
+ * The object can be moved to a different position.
+ */
+ const uint64_t MOVEABLE = ((uint64_t) 0x1) << 18;
+
+ /**
+ * The object describes itself with speech.
+ * Other speech related assistive technology may want to avoid speaking
+ * information about this object, because the object is already doing this.
+ */
+ const uint64_t SELFVOICING = ((uint64_t) 0x1) << 19;
+
+ /**
+ * The object can have the focus and become focused.
+ */
+ const uint64_t FOCUSABLE = ((uint64_t) 0x1) << 20;
+
+ /**
+ * The object can be selected.
+ */
+ const uint64_t SELECTABLE = ((uint64_t) 0x1) << 21;
+
+ /**
+ * This object is a link.
+ */
+ const uint64_t LINKED = ((uint64_t) 0x1) << 22;
+
+ /**
+ * This is used for links that have been traversed
+ * i.e. the linked page has been visited.
+ */
+ const uint64_t TRAVERSED = ((uint64_t) 0x1) << 23;
+
+ /**
+ * Supports multiple selection.
+ */
+ const uint64_t MULTISELECTABLE = ((uint64_t) 0x1) << 24;
+
+ /**
+ * Supports extended selection.
+ * All objects supporting this are also multipselectable.
+ * This only makes sense for msaa see bug 635690.
+ */
+ const uint64_t EXTSELECTABLE = ((uint64_t) 0x1) << 25;
+
+ /**
+ * The user is required to interact with this object.
+ */
+ const uint64_t REQUIRED = ((uint64_t) 0x1) << 26;
+
+ /**
+ * The object is an alert, notifying the user of something important.
+ */
+ const uint64_t ALERT = ((uint64_t) 0x1) << 27;
+
+ /**
+ * Used for text fields containing invalid values.
+ */
+ const uint64_t INVALID = ((uint64_t) 0x1) << 28;
+
+ /**
+ * The controls value can not be obtained, and is returned as a set of "*"s.
+ */
+ const uint64_t PROTECTED = ((uint64_t) 0x1) << 29;
+
+ /**
+ * The object can be invoked to show a pop up menu or window.
+ */
+ const uint64_t HASPOPUP = ((uint64_t) 0x1) << 30;
+
+ /**
+ * The editable area has some kind of autocompletion.
+ */
+ const uint64_t SUPPORTS_AUTOCOMPLETION = ((uint64_t) 0x1) << 31;
+
+ /**
+ * The object is no longer available to be queried.
+ */
+ const uint64_t DEFUNCT = ((uint64_t) 0x1) << 32;
+
+ /**
+ * The text is selectable, the object must implement the text interface.
+ */
+ const uint64_t SELECTABLE_TEXT = ((uint64_t) 0x1) << 33;
+
+ /**
+ * The text in this object can be edited.
+ */
+ const uint64_t EDITABLE = ((uint64_t) 0x1) << 34;
+
+ /**
+ * This window is currently the active window.
+ */
+ const uint64_t ACTIVE = ((uint64_t) 0x1) << 35;
+
+ /**
+ * Indicates that the object is modal. Modal objects have the behavior
+ * that something must be done with the object before the user can
+ * interact with an object in a different window.
+ */
+ const uint64_t MODAL = ((uint64_t) 0x1) << 36;
+
+ /**
+ * Edit control that can take multiple lines.
+ */
+ const uint64_t MULTI_LINE = ((uint64_t) 0x1) << 37;
+
+ /**
+ * Uses horizontal layout.
+ */
+ const uint64_t HORIZONTAL = ((uint64_t) 0x1) << 38;
+
+ /**
+ * Indicates this object paints every pixel within its rectangular region.
+ */
+ const uint64_t OPAQUE1 = ((uint64_t) 0x1) << 39;
+
+ /**
+ * This text object can only contain 1 line of text.
+ */
+ const uint64_t SINGLE_LINE = ((uint64_t) 0x1) << 40;
+
+ /**
+ * The parent object manages descendants, and this object may only exist
+ * while it is visible or has focus.
+ * For example the focused cell of a table or the current element of a list box may have this state.
+ */
+ const uint64_t TRANSIENT = ((uint64_t) 0x1) << 41;
+
+ /**
+ * Uses vertical layout.
+ * Especially used for sliders and scrollbars.
+ */
+ const uint64_t VERTICAL = ((uint64_t) 0x1) << 42;
+
+ /**
+ * Object not dead, but not up-to-date either.
+ */
+ const uint64_t STALE = ((uint64_t) 0x1) << 43;
+
+ /**
+ * A widget that is not unavailable.
+ */
+ const uint64_t ENABLED = ((uint64_t) 0x1) << 44;
+
+ /**
+ * Same as ENABLED state for now see bug 636158
+ */
+ const uint64_t SENSITIVE = ((uint64_t) 0x1) << 45;
+
+ /**
+ * The object is expandable, provides a UI to expand/collapse its children
+ * @see EXPANDED and COLLAPSED states.
+ */
+ const uint64_t EXPANDABLE = ((uint64_t) 0x1) << 46;
+
+ /**
+ * The object is pinned, usually indicating it is fixed in place and has permanence.
+ */
+ const uint64_t PINNED = ((uint64_t) 0x1) << 47;
+} // namespace states
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/base/Statistics.h b/accessible/base/Statistics.h
new file mode 100644
index 000000000..c9f1832b2
--- /dev/null
+++ b/accessible/base/Statistics.h
@@ -0,0 +1,39 @@
+/* -*- 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 A11Y_STATISTICS_H_
+#define A11Y_STATISTICS_H_
+
+#include "mozilla/Telemetry.h"
+
+namespace mozilla {
+namespace a11y {
+namespace statistics {
+
+ inline void A11yInitialized()
+ { Telemetry::Accumulate(Telemetry::A11Y_INSTANTIATED_FLAG, true); }
+
+ inline void A11yConsumers(uint32_t aConsumer)
+ { Telemetry::Accumulate(Telemetry::A11Y_CONSUMERS, aConsumer); }
+
+ /**
+ * Report that ISimpleDOM* has been used.
+ */
+ inline void ISimpleDOMUsed()
+ { Telemetry::Accumulate(Telemetry::A11Y_ISIMPLEDOM_USAGE_FLAG, true); }
+
+ /**
+ * Report that IAccessibleTable has been used.
+ */
+ inline void IAccessibleTableUsed()
+ { Telemetry::Accumulate(Telemetry::A11Y_IATABLE_USAGE_FLAG, true); }
+
+} // namespace statistics
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/base/StyleInfo.cpp b/accessible/base/StyleInfo.cpp
new file mode 100644
index 000000000..d49152cba
--- /dev/null
+++ b/accessible/base/StyleInfo.cpp
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set expandtab shiftwidth=2 tabstop=2: */
+/* 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 "StyleInfo.h"
+
+#include "mozilla/dom/Element.h"
+#include "nsComputedDOMStyle.h"
+#include "nsCSSProps.h"
+#include "nsIFrame.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+StyleInfo::StyleInfo(dom::Element* aElement, nsIPresShell* aPresShell) :
+ mElement(aElement)
+{
+ mStyleContext =
+ nsComputedDOMStyle::GetStyleContextForElementNoFlush(aElement,
+ nullptr,
+ aPresShell);
+}
+
+void
+StyleInfo::Display(nsAString& aValue)
+{
+ aValue.Truncate();
+ AppendASCIItoUTF16(
+ nsCSSProps::ValueToKeyword(mStyleContext->StyleDisplay()->mDisplay,
+ nsCSSProps::kDisplayKTable), aValue);
+}
+
+void
+StyleInfo::TextAlign(nsAString& aValue)
+{
+ aValue.Truncate();
+ AppendASCIItoUTF16(
+ nsCSSProps::ValueToKeyword(mStyleContext->StyleText()->mTextAlign,
+ nsCSSProps::kTextAlignKTable), aValue);
+}
+
+void
+StyleInfo::TextIndent(nsAString& aValue)
+{
+ aValue.Truncate();
+
+ const nsStyleCoord& styleCoord =
+ mStyleContext->StyleText()->mTextIndent;
+
+ nscoord coordVal = 0;
+ switch (styleCoord.GetUnit()) {
+ case eStyleUnit_Coord:
+ coordVal = styleCoord.GetCoordValue();
+ aValue.AppendFloat(nsPresContext::AppUnitsToFloatCSSPixels(coordVal));
+ aValue.AppendLiteral("px");
+ break;
+
+ case eStyleUnit_Percent:
+ aValue.AppendFloat(styleCoord.GetPercentValue() * 100);
+ aValue.AppendLiteral("%");
+ break;
+
+ case eStyleUnit_Null:
+ case eStyleUnit_Normal:
+ case eStyleUnit_Auto:
+ case eStyleUnit_None:
+ case eStyleUnit_Factor:
+ case eStyleUnit_Degree:
+ case eStyleUnit_Grad:
+ case eStyleUnit_Radian:
+ case eStyleUnit_Turn:
+ case eStyleUnit_FlexFraction:
+ case eStyleUnit_Integer:
+ case eStyleUnit_Enumerated:
+ case eStyleUnit_Calc:
+ aValue.AppendLiteral("0px");
+ break;
+ }
+}
+
+void
+StyleInfo::Margin(css::Side aSide, nsAString& aValue)
+{
+ MOZ_ASSERT(mElement->GetPrimaryFrame(), " mElement->GetPrimaryFrame() needs to be valid pointer");
+ aValue.Truncate();
+
+ nscoord coordVal = mElement->GetPrimaryFrame()->GetUsedMargin().Side(aSide);
+ aValue.AppendFloat(nsPresContext::AppUnitsToFloatCSSPixels(coordVal));
+ aValue.AppendLiteral("px");
+}
+
+void
+StyleInfo::FormatColor(const nscolor& aValue, nsString& aFormattedValue)
+{
+ // Combine the string like rgb(R, G, B) from nscolor.
+ aFormattedValue.AppendLiteral("rgb(");
+ aFormattedValue.AppendInt(NS_GET_R(aValue));
+ aFormattedValue.AppendLiteral(", ");
+ aFormattedValue.AppendInt(NS_GET_G(aValue));
+ aFormattedValue.AppendLiteral(", ");
+ aFormattedValue.AppendInt(NS_GET_B(aValue));
+ aFormattedValue.Append(')');
+}
+
+void
+StyleInfo::FormatFontStyle(const nscoord& aValue, nsAString& aFormattedValue)
+{
+ nsCSSKeyword keyword =
+ nsCSSProps::ValueToKeywordEnum(aValue, nsCSSProps::kFontStyleKTable);
+ AppendUTF8toUTF16(nsCSSKeywords::GetStringValue(keyword), aFormattedValue);
+}
+
+void
+StyleInfo::FormatTextDecorationStyle(uint8_t aValue, nsAString& aFormattedValue)
+{
+ nsCSSKeyword keyword =
+ nsCSSProps::ValueToKeywordEnum(aValue,
+ nsCSSProps::kTextDecorationStyleKTable);
+ AppendUTF8toUTF16(nsCSSKeywords::GetStringValue(keyword), aFormattedValue);
+}
diff --git a/accessible/base/StyleInfo.h b/accessible/base/StyleInfo.h
new file mode 100644
index 000000000..c8cc03388
--- /dev/null
+++ b/accessible/base/StyleInfo.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set expandtab shiftwidth=2 tabstop=2: */
+/* 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_style_h_
+#define _mozilla_a11y_style_h_
+
+#include "mozilla/gfx/Types.h"
+#include "nsStyleContext.h"
+
+namespace mozilla {
+namespace a11y {
+
+class StyleInfo
+{
+public:
+ StyleInfo(dom::Element* aElement, nsIPresShell* aPresShell);
+ ~StyleInfo() { }
+
+ void Display(nsAString& aValue);
+ void TextAlign(nsAString& aValue);
+ void TextIndent(nsAString& aValue);
+ void MarginLeft(nsAString& aValue) { Margin(eSideLeft, aValue); }
+ void MarginRight(nsAString& aValue) { Margin(eSideRight, aValue); }
+ void MarginTop(nsAString& aValue) { Margin(eSideTop, aValue); }
+ void MarginBottom(nsAString& aValue) { Margin(eSideBottom, aValue); }
+
+ static void FormatColor(const nscolor& aValue, nsString& aFormattedValue);
+ static void FormatFontStyle(const nscoord& aValue, nsAString& aFormattedValue);
+ static void FormatTextDecorationStyle(uint8_t aValue, nsAString& aFormattedValue);
+
+private:
+ StyleInfo() = delete;
+ StyleInfo(const StyleInfo&) = delete;
+ StyleInfo& operator = (const StyleInfo&) = delete;
+
+ void Margin(Side aSide, nsAString& aValue);
+
+ dom::Element* mElement;
+ RefPtr<nsStyleContext> mStyleContext;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/TextAttrs.cpp b/accessible/base/TextAttrs.cpp
new file mode 100644
index 000000000..0f9e4f6fe
--- /dev/null
+++ b/accessible/base/TextAttrs.cpp
@@ -0,0 +1,913 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "TextAttrs.h"
+
+#include "Accessible-inl.h"
+#include "nsAccUtils.h"
+#include "nsCoreUtils.h"
+#include "StyleInfo.h"
+
+#include "gfxFont.h"
+#include "nsFontMetrics.h"
+#include "nsLayoutUtils.h"
+#include "nsContainerFrame.h"
+#include "HyperTextAccessible.h"
+#include "mozilla/AppUnits.h"
+#include "mozilla/gfx/2D.h"
+
+#if defined(MOZ_WIDGET_GTK)
+#include "gfxPlatformGtk.h" // xxx - for UseFcFontList
+#endif
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// TextAttrsMgr
+////////////////////////////////////////////////////////////////////////////////
+
+void
+TextAttrsMgr::GetAttributes(nsIPersistentProperties* aAttributes,
+ uint32_t* aStartOffset,
+ uint32_t* aEndOffset)
+{
+ // 1. Hyper text accessible must be specified always.
+ // 2. Offset accessible and result hyper text offsets must be specified in
+ // the case of text attributes.
+ // 3. Offset accessible and result hyper text offsets must not be specified
+ // but include default text attributes flag and attributes list must be
+ // specified in the case of default text attributes.
+ NS_PRECONDITION(mHyperTextAcc &&
+ ((mOffsetAcc && mOffsetAccIdx != -1 &&
+ aStartOffset && aEndOffset) ||
+ (!mOffsetAcc && mOffsetAccIdx == -1 &&
+ !aStartOffset && !aEndOffset &&
+ mIncludeDefAttrs && aAttributes)),
+ "Wrong usage of TextAttrsMgr!");
+
+ // Embedded objects are combined into own range with empty attributes set.
+ if (mOffsetAcc && !mOffsetAcc->IsText()) {
+ for (int32_t childIdx = mOffsetAccIdx - 1; childIdx >= 0; childIdx--) {
+ Accessible* currAcc = mHyperTextAcc->GetChildAt(childIdx);
+ if (currAcc->IsText())
+ break;
+
+ (*aStartOffset)--;
+ }
+
+ uint32_t childCount = mHyperTextAcc->ChildCount();
+ for (uint32_t childIdx = mOffsetAccIdx + 1; childIdx < childCount;
+ childIdx++) {
+ Accessible* currAcc = mHyperTextAcc->GetChildAt(childIdx);
+ if (currAcc->IsText())
+ break;
+
+ (*aEndOffset)++;
+ }
+
+ return;
+ }
+
+ // Get the content and frame of the accessible. In the case of document
+ // accessible it's role content and root frame.
+ nsIContent* hyperTextElm = mHyperTextAcc->GetContent();
+ if (!hyperTextElm)
+ return; // XXX: we don't support text attrs on document with no body
+
+ nsIFrame* rootFrame = mHyperTextAcc->GetFrame();
+ MOZ_ASSERT(rootFrame, "No frame for accessible!");
+ if (!rootFrame)
+ return;
+
+ nsIContent *offsetNode = nullptr, *offsetElm = nullptr;
+ nsIFrame *frame = nullptr;
+ if (mOffsetAcc) {
+ offsetNode = mOffsetAcc->GetContent();
+ offsetElm = nsCoreUtils::GetDOMElementFor(offsetNode);
+ MOZ_ASSERT(offsetElm, "No element for offset accessible!");
+ if (!offsetElm)
+ return;
+
+ frame = offsetElm->GetPrimaryFrame();
+ }
+
+ // "language" text attribute
+ LangTextAttr langTextAttr(mHyperTextAcc, hyperTextElm, offsetNode);
+
+ // "aria-invalid" text attribute
+ InvalidTextAttr invalidTextAttr(hyperTextElm, offsetNode);
+
+ // "background-color" text attribute
+ BGColorTextAttr bgColorTextAttr(rootFrame, frame);
+
+ // "color" text attribute
+ ColorTextAttr colorTextAttr(rootFrame, frame);
+
+ // "font-family" text attribute
+ FontFamilyTextAttr fontFamilyTextAttr(rootFrame, frame);
+
+ // "font-size" text attribute
+ FontSizeTextAttr fontSizeTextAttr(rootFrame, frame);
+
+ // "font-style" text attribute
+ FontStyleTextAttr fontStyleTextAttr(rootFrame, frame);
+
+ // "font-weight" text attribute
+ FontWeightTextAttr fontWeightTextAttr(rootFrame, frame);
+
+ // "auto-generated" text attribute
+ AutoGeneratedTextAttr autoGenTextAttr(mHyperTextAcc, mOffsetAcc);
+
+ // "text-underline(line-through)-style(color)" text attributes
+ TextDecorTextAttr textDecorTextAttr(rootFrame, frame);
+
+ // "text-position" text attribute
+ TextPosTextAttr textPosTextAttr(rootFrame, frame);
+
+ TextAttr* attrArray[] =
+ {
+ &langTextAttr,
+ &invalidTextAttr,
+ &bgColorTextAttr,
+ &colorTextAttr,
+ &fontFamilyTextAttr,
+ &fontSizeTextAttr,
+ &fontStyleTextAttr,
+ &fontWeightTextAttr,
+ &autoGenTextAttr,
+ &textDecorTextAttr,
+ &textPosTextAttr
+ };
+
+ // Expose text attributes if applicable.
+ if (aAttributes) {
+ for (uint32_t idx = 0; idx < ArrayLength(attrArray); idx++)
+ attrArray[idx]->Expose(aAttributes, mIncludeDefAttrs);
+ }
+
+ // Expose text attributes range where they are applied if applicable.
+ if (mOffsetAcc)
+ GetRange(attrArray, ArrayLength(attrArray), aStartOffset, aEndOffset);
+}
+
+void
+TextAttrsMgr::GetRange(TextAttr* aAttrArray[], uint32_t aAttrArrayLen,
+ uint32_t* aStartOffset, uint32_t* aEndOffset)
+{
+ // Navigate backward from anchor accessible to find start offset.
+ for (int32_t childIdx = mOffsetAccIdx - 1; childIdx >= 0; childIdx--) {
+ Accessible* currAcc = mHyperTextAcc->GetChildAt(childIdx);
+
+ // Stop on embedded accessible since embedded accessibles are combined into
+ // own range.
+ if (!currAcc->IsText())
+ break;
+
+ MOZ_ASSERT(nsCoreUtils::GetDOMElementFor(currAcc->GetContent()),
+ "Text accessible has to have an associated DOM element");
+
+ bool offsetFound = false;
+ for (uint32_t attrIdx = 0; attrIdx < aAttrArrayLen; attrIdx++) {
+ TextAttr* textAttr = aAttrArray[attrIdx];
+ if (!textAttr->Equal(currAcc)) {
+ offsetFound = true;
+ break;
+ }
+ }
+
+ if (offsetFound)
+ break;
+
+ *(aStartOffset) -= nsAccUtils::TextLength(currAcc);
+ }
+
+ // Navigate forward from anchor accessible to find end offset.
+ uint32_t childLen = mHyperTextAcc->ChildCount();
+ for (uint32_t childIdx = mOffsetAccIdx + 1; childIdx < childLen; childIdx++) {
+ Accessible* currAcc = mHyperTextAcc->GetChildAt(childIdx);
+ if (!currAcc->IsText())
+ break;
+
+ MOZ_ASSERT(nsCoreUtils::GetDOMElementFor(currAcc->GetContent()),
+ "Text accessible has to have an associated DOM element");
+
+ bool offsetFound = false;
+ for (uint32_t attrIdx = 0; attrIdx < aAttrArrayLen; attrIdx++) {
+ TextAttr* textAttr = aAttrArray[attrIdx];
+
+ // Alter the end offset when text attribute changes its value and stop
+ // the search.
+ if (!textAttr->Equal(currAcc)) {
+ offsetFound = true;
+ break;
+ }
+ }
+
+ if (offsetFound)
+ break;
+
+ (*aEndOffset) += nsAccUtils::TextLength(currAcc);
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// LangTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+TextAttrsMgr::LangTextAttr::
+ LangTextAttr(HyperTextAccessible* aRoot,
+ nsIContent* aRootElm, nsIContent* aElm) :
+ TTextAttr<nsString>(!aElm), mRootContent(aRootElm)
+{
+ aRoot->Language(mRootNativeValue);
+ mIsRootDefined = !mRootNativeValue.IsEmpty();
+
+ if (aElm) {
+ nsCoreUtils::GetLanguageFor(aElm, mRootContent, mNativeValue);
+ mIsDefined = !mNativeValue.IsEmpty();
+ }
+}
+
+TextAttrsMgr::LangTextAttr::
+ ~LangTextAttr() {}
+
+bool
+TextAttrsMgr::LangTextAttr::
+ GetValueFor(Accessible* aAccessible, nsString* aValue)
+{
+ nsCoreUtils::GetLanguageFor(aAccessible->GetContent(), mRootContent, *aValue);
+ return !aValue->IsEmpty();
+}
+
+void
+TextAttrsMgr::LangTextAttr::
+ ExposeValue(nsIPersistentProperties* aAttributes, const nsString& aValue)
+{
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::language, aValue);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// InvalidTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+TextAttrsMgr::InvalidTextAttr::
+ InvalidTextAttr(nsIContent* aRootElm, nsIContent* aElm) :
+ TTextAttr<uint32_t>(!aElm), mRootElm(aRootElm)
+{
+ mIsRootDefined = GetValue(mRootElm, &mRootNativeValue);
+ if (aElm)
+ mIsDefined = GetValue(aElm, &mNativeValue);
+}
+
+bool
+TextAttrsMgr::InvalidTextAttr::
+ GetValueFor(Accessible* aAccessible, uint32_t* aValue)
+{
+ nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
+ return elm ? GetValue(elm, aValue) : false;
+}
+
+void
+TextAttrsMgr::InvalidTextAttr::
+ ExposeValue(nsIPersistentProperties* aAttributes, const uint32_t& aValue)
+{
+ switch (aValue) {
+ case eFalse:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid,
+ NS_LITERAL_STRING("false"));
+ break;
+
+ case eGrammar:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid,
+ NS_LITERAL_STRING("grammar"));
+ break;
+
+ case eSpelling:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid,
+ NS_LITERAL_STRING("spelling"));
+ break;
+
+ case eTrue:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid,
+ NS_LITERAL_STRING("true"));
+ break;
+ }
+}
+
+bool
+TextAttrsMgr::InvalidTextAttr::
+ GetValue(nsIContent* aElm, uint32_t* aValue)
+{
+ nsIContent* elm = aElm;
+ do {
+ if (nsAccUtils::HasDefinedARIAToken(elm, nsGkAtoms::aria_invalid)) {
+ static nsIContent::AttrValuesArray tokens[] =
+ { &nsGkAtoms::_false, &nsGkAtoms::grammar, &nsGkAtoms::spelling,
+ nullptr };
+
+ int32_t idx = elm->FindAttrValueIn(kNameSpaceID_None,
+ nsGkAtoms::aria_invalid, tokens,
+ eCaseMatters);
+ switch (idx) {
+ case 0:
+ *aValue = eFalse;
+ return true;
+ case 1:
+ *aValue = eGrammar;
+ return true;
+ case 2:
+ *aValue = eSpelling;
+ return true;
+ default:
+ *aValue = eTrue;
+ return true;
+ }
+ }
+ } while ((elm = elm->GetParent()) && elm != mRootElm);
+
+ return false;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// BGColorTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+TextAttrsMgr::BGColorTextAttr::
+ BGColorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
+ TTextAttr<nscolor>(!aFrame), mRootFrame(aRootFrame)
+{
+ mIsRootDefined = GetColor(mRootFrame, &mRootNativeValue);
+ if (aFrame)
+ mIsDefined = GetColor(aFrame, &mNativeValue);
+}
+
+bool
+TextAttrsMgr::BGColorTextAttr::
+ GetValueFor(Accessible* aAccessible, nscolor* aValue)
+{
+ nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
+ if (elm) {
+ nsIFrame* frame = elm->GetPrimaryFrame();
+ if (frame) {
+ return GetColor(frame, aValue);
+ }
+ }
+ return false;
+}
+
+void
+TextAttrsMgr::BGColorTextAttr::
+ ExposeValue(nsIPersistentProperties* aAttributes, const nscolor& aValue)
+{
+ nsAutoString formattedValue;
+ StyleInfo::FormatColor(aValue, formattedValue);
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::backgroundColor,
+ formattedValue);
+}
+
+bool
+TextAttrsMgr::BGColorTextAttr::
+ GetColor(nsIFrame* aFrame, nscolor* aColor)
+{
+ const nsStyleBackground* styleBackground = aFrame->StyleBackground();
+
+ if (NS_GET_A(styleBackground->mBackgroundColor) > 0) {
+ *aColor = styleBackground->mBackgroundColor;
+ return true;
+ }
+
+ nsContainerFrame *parentFrame = aFrame->GetParent();
+ if (!parentFrame) {
+ *aColor = aFrame->PresContext()->DefaultBackgroundColor();
+ return true;
+ }
+
+ // Each frame of parents chain for the initially passed 'aFrame' has
+ // transparent background color. So background color isn't changed from
+ // 'mRootFrame' to initially passed 'aFrame'.
+ if (parentFrame == mRootFrame)
+ return false;
+
+ return GetColor(parentFrame, aColor);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// ColorTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+TextAttrsMgr::ColorTextAttr::
+ ColorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
+ TTextAttr<nscolor>(!aFrame)
+{
+ mRootNativeValue = aRootFrame->StyleColor()->mColor;
+ mIsRootDefined = true;
+
+ if (aFrame) {
+ mNativeValue = aFrame->StyleColor()->mColor;
+ mIsDefined = true;
+ }
+}
+
+bool
+TextAttrsMgr::ColorTextAttr::
+ GetValueFor(Accessible* aAccessible, nscolor* aValue)
+{
+ nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
+ if (elm) {
+ nsIFrame* frame = elm->GetPrimaryFrame();
+ if (frame) {
+ *aValue = frame->StyleColor()->mColor;
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+TextAttrsMgr::ColorTextAttr::
+ ExposeValue(nsIPersistentProperties* aAttributes, const nscolor& aValue)
+{
+ nsAutoString formattedValue;
+ StyleInfo::FormatColor(aValue, formattedValue);
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::color, formattedValue);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// FontFamilyTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+TextAttrsMgr::FontFamilyTextAttr::
+ FontFamilyTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
+ TTextAttr<nsString>(!aFrame)
+{
+ mIsRootDefined = GetFontFamily(aRootFrame, mRootNativeValue);
+
+ if (aFrame)
+ mIsDefined = GetFontFamily(aFrame, mNativeValue);
+}
+
+bool
+TextAttrsMgr::FontFamilyTextAttr::
+ GetValueFor(Accessible* aAccessible, nsString* aValue)
+{
+ nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
+ if (elm) {
+ nsIFrame* frame = elm->GetPrimaryFrame();
+ if (frame) {
+ return GetFontFamily(frame, *aValue);
+ }
+ }
+ return false;
+}
+
+void
+TextAttrsMgr::FontFamilyTextAttr::
+ ExposeValue(nsIPersistentProperties* aAttributes, const nsString& aValue)
+{
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::font_family, aValue);
+}
+
+bool
+TextAttrsMgr::FontFamilyTextAttr::
+ GetFontFamily(nsIFrame* aFrame, nsString& aFamily)
+{
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(aFrame, 1.0f);
+
+ gfxFontGroup* fontGroup = fm->GetThebesFontGroup();
+ gfxFont* font = fontGroup->GetFirstValidFont();
+ gfxFontEntry* fontEntry = font->GetFontEntry();
+ aFamily = fontEntry->FamilyName();
+ return true;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// FontSizeTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+TextAttrsMgr::FontSizeTextAttr::
+ FontSizeTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
+ TTextAttr<nscoord>(!aFrame)
+{
+ mDC = aRootFrame->PresContext()->DeviceContext();
+
+ mRootNativeValue = aRootFrame->StyleFont()->mSize;
+ mIsRootDefined = true;
+
+ if (aFrame) {
+ mNativeValue = aFrame->StyleFont()->mSize;
+ mIsDefined = true;
+ }
+}
+
+bool
+TextAttrsMgr::FontSizeTextAttr::
+ GetValueFor(Accessible* aAccessible, nscoord* aValue)
+{
+ nsIContent* el = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
+ if (el) {
+ nsIFrame* frame = el->GetPrimaryFrame();
+ if (frame) {
+ *aValue = frame->StyleFont()->mSize;
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+TextAttrsMgr::FontSizeTextAttr::
+ ExposeValue(nsIPersistentProperties* aAttributes, const nscoord& aValue)
+{
+ // Convert from nscoord to pt.
+ //
+ // Note: according to IA2, "The conversion doesn't have to be exact.
+ // The intent is to give the user a feel for the size of the text."
+ //
+ // ATK does not specify a unit and will likely follow IA2 here.
+ //
+ // XXX todo: consider sharing this code with layout module? (bug 474621)
+ float px =
+ NSAppUnitsToFloatPixels(aValue, mozilla::AppUnitsPerCSSPixel());
+ // Each pt is 4/3 of a CSS pixel.
+ int pts = NS_lround(px*3/4);
+
+ nsAutoString value;
+ value.AppendInt(pts);
+ value.AppendLiteral("pt");
+
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::font_size, value);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// FontStyleTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+TextAttrsMgr::FontStyleTextAttr::
+ FontStyleTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
+ TTextAttr<nscoord>(!aFrame)
+{
+ mRootNativeValue = aRootFrame->StyleFont()->mFont.style;
+ mIsRootDefined = true;
+
+ if (aFrame) {
+ mNativeValue = aFrame->StyleFont()->mFont.style;
+ mIsDefined = true;
+ }
+}
+
+bool
+TextAttrsMgr::FontStyleTextAttr::
+ GetValueFor(Accessible* aAccessible, nscoord* aValue)
+{
+ nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
+ if (elm) {
+ nsIFrame* frame = elm->GetPrimaryFrame();
+ if (frame) {
+ *aValue = frame->StyleFont()->mFont.style;
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+TextAttrsMgr::FontStyleTextAttr::
+ ExposeValue(nsIPersistentProperties* aAttributes, const nscoord& aValue)
+{
+ nsAutoString formattedValue;
+ StyleInfo::FormatFontStyle(aValue, formattedValue);
+
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::font_style, formattedValue);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// FontWeightTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+TextAttrsMgr::FontWeightTextAttr::
+ FontWeightTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
+ TTextAttr<int32_t>(!aFrame)
+{
+ mRootNativeValue = GetFontWeight(aRootFrame);
+ mIsRootDefined = true;
+
+ if (aFrame) {
+ mNativeValue = GetFontWeight(aFrame);
+ mIsDefined = true;
+ }
+}
+
+bool
+TextAttrsMgr::FontWeightTextAttr::
+ GetValueFor(Accessible* aAccessible, int32_t* aValue)
+{
+ nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
+ if (elm) {
+ nsIFrame* frame = elm->GetPrimaryFrame();
+ if (frame) {
+ *aValue = GetFontWeight(frame);
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+TextAttrsMgr::FontWeightTextAttr::
+ ExposeValue(nsIPersistentProperties* aAttributes, const int32_t& aValue)
+{
+ nsAutoString formattedValue;
+ formattedValue.AppendInt(aValue);
+
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::fontWeight, formattedValue);
+}
+
+int32_t
+TextAttrsMgr::FontWeightTextAttr::
+ GetFontWeight(nsIFrame* aFrame)
+{
+ // nsFont::width isn't suitable here because it's necessary to expose real
+ // value of font weight (used font might not have some font weight values).
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(aFrame, 1.0f);
+
+ gfxFontGroup *fontGroup = fm->GetThebesFontGroup();
+ gfxFont *font = fontGroup->GetFirstValidFont();
+
+ // When there doesn't exist a bold font in the family and so the rendering of
+ // a non-bold font face is changed so that the user sees what looks like a
+ // bold font, i.e. synthetic bolding is used. IsSyntheticBold method is only
+ // needed on Mac, but it is "safe" to use on all platforms. (For non-Mac
+ // platforms it always return false.)
+ if (font->IsSyntheticBold())
+ return 700;
+
+ bool useFontEntryWeight = true;
+
+ // Under Linux, when gfxPangoFontGroup code is used,
+ // font->GetStyle()->weight will give the absolute weight requested of the
+ // font face. The gfxPangoFontGroup code uses the gfxFontEntry constructor
+ // which doesn't initialize the weight field.
+#if defined(MOZ_WIDGET_GTK)
+ useFontEntryWeight = gfxPlatformGtk::UseFcFontList();
+#endif
+
+ if (useFontEntryWeight) {
+ // On Windows, font->GetStyle()->weight will give the same weight as
+ // fontEntry->Weight(), the weight of the first font in the font group,
+ // which may not be the weight of the font face used to render the
+ // characters. On Mac, font->GetStyle()->weight will just give the same
+ // number as getComputedStyle(). fontEntry->Weight() will give the weight
+ // of the font face used.
+ gfxFontEntry *fontEntry = font->GetFontEntry();
+ return fontEntry->Weight();
+ } else {
+ return font->GetStyle()->weight;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AutoGeneratedTextAttr
+////////////////////////////////////////////////////////////////////////////////
+TextAttrsMgr::AutoGeneratedTextAttr::
+ AutoGeneratedTextAttr(HyperTextAccessible* aHyperTextAcc,
+ Accessible* aAccessible) :
+ TTextAttr<bool>(!aAccessible)
+{
+ mRootNativeValue = false;
+ mIsRootDefined = false;
+
+ if (aAccessible)
+ mIsDefined = mNativeValue = (aAccessible->NativeRole() == roles::STATICTEXT);
+}
+
+bool
+TextAttrsMgr::AutoGeneratedTextAttr::
+ GetValueFor(Accessible* aAccessible, bool* aValue)
+{
+ return *aValue = (aAccessible->NativeRole() == roles::STATICTEXT);
+}
+
+void
+TextAttrsMgr::AutoGeneratedTextAttr::
+ ExposeValue(nsIPersistentProperties* aAttributes, const bool& aValue)
+{
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::auto_generated,
+ aValue ? NS_LITERAL_STRING("true") : NS_LITERAL_STRING("false"));
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// TextDecorTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+TextAttrsMgr::TextDecorValue::
+ TextDecorValue(nsIFrame* aFrame)
+{
+ const nsStyleTextReset* textReset = aFrame->StyleTextReset();
+ mStyle = textReset->mTextDecorationStyle;
+ mColor = aFrame->StyleColor()->
+ CalcComplexColor(textReset->mTextDecorationColor);
+ mLine = textReset->mTextDecorationLine &
+ (NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE |
+ NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH);
+}
+
+TextAttrsMgr::TextDecorTextAttr::
+ TextDecorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
+ TTextAttr<TextDecorValue>(!aFrame)
+{
+ mRootNativeValue = TextDecorValue(aRootFrame);
+ mIsRootDefined = mRootNativeValue.IsDefined();
+
+ if (aFrame) {
+ mNativeValue = TextDecorValue(aFrame);
+ mIsDefined = mNativeValue.IsDefined();
+ }
+}
+
+bool
+TextAttrsMgr::TextDecorTextAttr::
+ GetValueFor(Accessible* aAccessible, TextDecorValue* aValue)
+{
+ nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
+ if (elm) {
+ nsIFrame* frame = elm->GetPrimaryFrame();
+ if (frame) {
+ *aValue = TextDecorValue(frame);
+ return aValue->IsDefined();
+ }
+ }
+ return false;
+}
+
+void
+TextAttrsMgr::TextDecorTextAttr::
+ ExposeValue(nsIPersistentProperties* aAttributes, const TextDecorValue& aValue)
+{
+ if (aValue.IsUnderline()) {
+ nsAutoString formattedStyle;
+ StyleInfo::FormatTextDecorationStyle(aValue.Style(), formattedStyle);
+ nsAccUtils::SetAccAttr(aAttributes,
+ nsGkAtoms::textUnderlineStyle,
+ formattedStyle);
+
+ nsAutoString formattedColor;
+ StyleInfo::FormatColor(aValue.Color(), formattedColor);
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textUnderlineColor,
+ formattedColor);
+ return;
+ }
+
+ if (aValue.IsLineThrough()) {
+ nsAutoString formattedStyle;
+ StyleInfo::FormatTextDecorationStyle(aValue.Style(), formattedStyle);
+ nsAccUtils::SetAccAttr(aAttributes,
+ nsGkAtoms::textLineThroughStyle,
+ formattedStyle);
+
+ nsAutoString formattedColor;
+ StyleInfo::FormatColor(aValue.Color(), formattedColor);
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textLineThroughColor,
+ formattedColor);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TextPosTextAttr
+////////////////////////////////////////////////////////////////////////////////
+
+TextAttrsMgr::TextPosTextAttr::
+ TextPosTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
+ TTextAttr<TextPosValue>(!aFrame)
+{
+ mRootNativeValue = GetTextPosValue(aRootFrame);
+ mIsRootDefined = mRootNativeValue != eTextPosNone;
+
+ if (aFrame) {
+ mNativeValue = GetTextPosValue(aFrame);
+ mIsDefined = mNativeValue != eTextPosNone;
+ }
+}
+
+bool
+TextAttrsMgr::TextPosTextAttr::
+ GetValueFor(Accessible* aAccessible, TextPosValue* aValue)
+{
+ nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
+ if (elm) {
+ nsIFrame* frame = elm->GetPrimaryFrame();
+ if (frame) {
+ *aValue = GetTextPosValue(frame);
+ return *aValue != eTextPosNone;
+ }
+ }
+ return false;
+}
+
+void
+TextAttrsMgr::TextPosTextAttr::
+ ExposeValue(nsIPersistentProperties* aAttributes, const TextPosValue& aValue)
+{
+ switch (aValue) {
+ case eTextPosBaseline:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textPosition,
+ NS_LITERAL_STRING("baseline"));
+ break;
+
+ case eTextPosSub:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textPosition,
+ NS_LITERAL_STRING("sub"));
+ break;
+
+ case eTextPosSuper:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textPosition,
+ NS_LITERAL_STRING("super"));
+ break;
+
+ case eTextPosNone:
+ break;
+ }
+}
+
+TextAttrsMgr::TextPosValue
+TextAttrsMgr::TextPosTextAttr::
+ GetTextPosValue(nsIFrame* aFrame) const
+{
+ const nsStyleCoord& styleCoord = aFrame->StyleDisplay()->mVerticalAlign;
+ switch (styleCoord.GetUnit()) {
+ case eStyleUnit_Enumerated:
+ switch (styleCoord.GetIntValue()) {
+ case NS_STYLE_VERTICAL_ALIGN_BASELINE:
+ return eTextPosBaseline;
+ case NS_STYLE_VERTICAL_ALIGN_SUB:
+ return eTextPosSub;
+ case NS_STYLE_VERTICAL_ALIGN_SUPER:
+ return eTextPosSuper;
+
+ // No good guess for these:
+ // NS_STYLE_VERTICAL_ALIGN_TOP
+ // NS_STYLE_VERTICAL_ALIGN_TEXT_TOP
+ // NS_STYLE_VERTICAL_ALIGN_MIDDLE
+ // NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM
+ // NS_STYLE_VERTICAL_ALIGN_BOTTOM
+ // NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE
+ // Do not expose value of text-position attribute.
+
+ default:
+ break;
+ }
+ return eTextPosNone;
+
+ case eStyleUnit_Percent:
+ {
+ float percentValue = styleCoord.GetPercentValue();
+ return percentValue > 0 ?
+ eTextPosSuper :
+ (percentValue < 0 ? eTextPosSub : eTextPosBaseline);
+ }
+
+ case eStyleUnit_Coord:
+ {
+ nscoord coordValue = styleCoord.GetCoordValue();
+ return coordValue > 0 ?
+ eTextPosSuper :
+ (coordValue < 0 ? eTextPosSub : eTextPosBaseline);
+ }
+
+ case eStyleUnit_Null:
+ case eStyleUnit_Normal:
+ case eStyleUnit_Auto:
+ case eStyleUnit_None:
+ case eStyleUnit_Factor:
+ case eStyleUnit_Degree:
+ case eStyleUnit_Grad:
+ case eStyleUnit_Radian:
+ case eStyleUnit_Turn:
+ case eStyleUnit_FlexFraction:
+ case eStyleUnit_Integer:
+ case eStyleUnit_Calc:
+ break;
+ }
+
+ const nsIContent* content = aFrame->GetContent();
+ if (content) {
+ if (content->IsHTMLElement(nsGkAtoms::sup))
+ return eTextPosSuper;
+ if (content->IsHTMLElement(nsGkAtoms::sub))
+ return eTextPosSub;
+ }
+
+ return eTextPosNone;
+}
diff --git a/accessible/base/TextAttrs.h b/accessible/base/TextAttrs.h
new file mode 100644
index 000000000..a8db5a1d1
--- /dev/null
+++ b/accessible/base/TextAttrs.h
@@ -0,0 +1,478 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 nsTextAttrs_h_
+#define nsTextAttrs_h_
+
+#include "nsCOMPtr.h"
+#include "nsColor.h"
+#include "nsStyleConsts.h"
+
+class nsIFrame;
+class nsIPersistentProperties;
+class nsIContent;
+class nsDeviceContext;
+
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+class HyperTextAccessible;
+
+/**
+ * Used to expose text attributes for the hyper text accessible (see
+ * HyperTextAccessible class).
+ *
+ * @note "invalid: spelling" text attribute is implemented entirely in
+ * HyperTextAccessible class.
+ */
+class TextAttrsMgr
+{
+public:
+ /**
+ * Constructor. Used to expose default text attributes.
+ */
+ explicit TextAttrsMgr(HyperTextAccessible* aHyperTextAcc) :
+ mOffsetAcc(nullptr), mHyperTextAcc(aHyperTextAcc),
+ mOffsetAccIdx(-1), mIncludeDefAttrs(true) { }
+
+ /**
+ * Constructor. Used to expose text attributes at the given offset.
+ *
+ * @param aHyperTextAcc [in] hyper text accessible text attributes are
+ * calculated for
+ * @param aIncludeDefAttrs [optional] indicates whether default text
+ * attributes should be included into list of exposed
+ * text attributes
+ * @param oOffsetAcc [optional] offset an accessible the text attributes
+ * should be calculated for
+ * @param oOffsetAccIdx [optional] index in parent of offset accessible
+ */
+ TextAttrsMgr(HyperTextAccessible* aHyperTextAcc,
+ bool aIncludeDefAttrs,
+ Accessible* aOffsetAcc,
+ int32_t aOffsetAccIdx) :
+ mOffsetAcc(aOffsetAcc), mHyperTextAcc(aHyperTextAcc),
+ mOffsetAccIdx(aOffsetAccIdx), mIncludeDefAttrs(aIncludeDefAttrs) { }
+
+ /*
+ * Return text attributes and hyper text offsets where these attributes are
+ * applied. Offsets are calculated in the case of non default attributes.
+ *
+ * @note In the case of default attributes pointers on hyper text offsets
+ * must be skipped.
+ *
+ * @param aAttributes [in, out] text attributes list
+ * @param aStartHTOffset [out, optional] start hyper text offset
+ * @param aEndHTOffset [out, optional] end hyper text offset
+ */
+ void GetAttributes(nsIPersistentProperties* aAttributes,
+ uint32_t* aStartHTOffset = nullptr,
+ uint32_t* aEndHTOffset = nullptr);
+
+protected:
+ /**
+ * Calculates range (start and end offsets) of text where the text attributes
+ * are stretched. New offsets may be smaller if one of text attributes changes
+ * its value before or after the given offsets.
+ *
+ * @param aTextAttrArray [in] text attributes array
+ * @param aAttrArrayLen [in] text attributes array length
+ * @param aStartHTOffset [in, out] the start offset
+ * @param aEndHTOffset [in, out] the end offset
+ */
+ class TextAttr;
+ void GetRange(TextAttr* aAttrArray[], uint32_t aAttrArrayLen,
+ uint32_t* aStartOffset, uint32_t* aEndOffset);
+
+private:
+ Accessible* mOffsetAcc;
+ HyperTextAccessible* mHyperTextAcc;
+ int32_t mOffsetAccIdx;
+ bool mIncludeDefAttrs;
+
+protected:
+
+ /**
+ * Interface class of text attribute class implementations.
+ */
+ class TextAttr
+ {
+ public:
+ /**
+ * Expose the text attribute to the given attribute set.
+ *
+ * @param aAttributes [in] the given attribute set
+ * @param aIncludeDefAttrValue [in] if true then attribute is exposed even
+ * if its value is the same as default one
+ */
+ virtual void Expose(nsIPersistentProperties* aAttributes,
+ bool aIncludeDefAttrValue) = 0;
+
+ /**
+ * Return true if the text attribute value on the given element equals with
+ * predefined attribute value.
+ */
+ virtual bool Equal(Accessible* aAccessible) = 0;
+ };
+
+
+ /**
+ * Base class to work with text attributes. See derived classes below.
+ */
+ template<class T>
+ class TTextAttr : public TextAttr
+ {
+ public:
+ explicit TTextAttr(bool aGetRootValue) : mGetRootValue(aGetRootValue) {}
+
+ // TextAttr
+ virtual void Expose(nsIPersistentProperties* aAttributes,
+ bool aIncludeDefAttrValue) override
+ {
+ if (mGetRootValue) {
+ if (mIsRootDefined)
+ ExposeValue(aAttributes, mRootNativeValue);
+ return;
+ }
+
+ if (mIsDefined) {
+ if (aIncludeDefAttrValue || mRootNativeValue != mNativeValue)
+ ExposeValue(aAttributes, mNativeValue);
+ return;
+ }
+
+ if (aIncludeDefAttrValue && mIsRootDefined)
+ ExposeValue(aAttributes, mRootNativeValue);
+ }
+
+ virtual bool Equal(Accessible* aAccessible) override
+ {
+ T nativeValue;
+ bool isDefined = GetValueFor(aAccessible, &nativeValue);
+
+ if (!mIsDefined && !isDefined)
+ return true;
+
+ if (mIsDefined && isDefined)
+ return nativeValue == mNativeValue;
+
+ if (mIsDefined)
+ return mNativeValue == mRootNativeValue;
+
+ return nativeValue == mRootNativeValue;
+ }
+
+ protected:
+
+ // Expose the text attribute with the given value to attribute set.
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const T& aValue) = 0;
+
+ // Return native value for the given DOM element.
+ virtual bool GetValueFor(Accessible* aAccessible, T* aValue) = 0;
+
+ // Indicates if root value should be exposed.
+ bool mGetRootValue;
+
+ // Native value and flag indicating if the value is defined (initialized in
+ // derived classes). Note, undefined native value means it is inherited
+ // from root.
+ MOZ_INIT_OUTSIDE_CTOR T mNativeValue;
+ MOZ_INIT_OUTSIDE_CTOR bool mIsDefined;
+
+ // Native root value and flag indicating if the value is defined (initialized
+ // in derived classes).
+ MOZ_INIT_OUTSIDE_CTOR T mRootNativeValue;
+ MOZ_INIT_OUTSIDE_CTOR bool mIsRootDefined;
+ };
+
+
+ /**
+ * Class is used for the work with 'language' text attribute.
+ */
+ class LangTextAttr : public TTextAttr<nsString>
+ {
+ public:
+ LangTextAttr(HyperTextAccessible* aRoot, nsIContent* aRootElm,
+ nsIContent* aElm);
+ virtual ~LangTextAttr();
+
+ protected:
+
+ // TextAttr
+ virtual bool GetValueFor(Accessible* aAccessible, nsString* aValue) override;
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const nsString& aValue) override;
+
+ private:
+ nsCOMPtr<nsIContent> mRootContent;
+ };
+
+
+ /**
+ * Class is used for the 'invalid' text attribute. Note, it calculated
+ * the attribute from aria-invalid attribute only; invalid:spelling attribute
+ * calculated from misspelled text in the editor is managed by
+ * HyperTextAccessible and applied on top of the value from aria-invalid.
+ */
+ class InvalidTextAttr : public TTextAttr<uint32_t>
+ {
+ public:
+ InvalidTextAttr(nsIContent* aRootElm, nsIContent* aElm);
+ virtual ~InvalidTextAttr() { };
+
+ protected:
+
+ enum {
+ eFalse,
+ eGrammar,
+ eSpelling,
+ eTrue
+ };
+
+ // TextAttr
+ virtual bool GetValueFor(Accessible* aAccessible, uint32_t* aValue) override;
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const uint32_t& aValue) override;
+
+ private:
+ bool GetValue(nsIContent* aElm, uint32_t* aValue);
+ nsIContent* mRootElm;
+ };
+
+
+ /**
+ * Class is used for the work with 'background-color' text attribute.
+ */
+ class BGColorTextAttr : public TTextAttr<nscolor>
+ {
+ public:
+ BGColorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
+ virtual ~BGColorTextAttr() { }
+
+ protected:
+
+ // TextAttr
+ virtual bool GetValueFor(Accessible* aAccessible, nscolor* aValue)
+ override;
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const nscolor& aValue) override;
+
+ private:
+ bool GetColor(nsIFrame* aFrame, nscolor* aColor);
+ nsIFrame* mRootFrame;
+ };
+
+
+ /**
+ * Class is used for the work with 'color' text attribute.
+ */
+ class ColorTextAttr : public TTextAttr<nscolor>
+ {
+ public:
+ ColorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
+ virtual ~ColorTextAttr() { }
+
+ protected:
+
+ // TTextAttr
+ virtual bool GetValueFor(Accessible* aAccessible, nscolor* aValue)
+ override;
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const nscolor& aValue) override;
+ };
+
+
+ /**
+ * Class is used for the work with "font-family" text attribute.
+ */
+ class FontFamilyTextAttr : public TTextAttr<nsString>
+ {
+ public:
+ FontFamilyTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
+ virtual ~FontFamilyTextAttr() { }
+
+ protected:
+
+ // TTextAttr
+ virtual bool GetValueFor(Accessible* aAccessible, nsString* aValue)
+ override;
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const nsString& aValue) override;
+
+ private:
+
+ bool GetFontFamily(nsIFrame* aFrame, nsString& aFamily);
+ };
+
+
+ /**
+ * Class is used for the work with "font-size" text attribute.
+ */
+ class FontSizeTextAttr : public TTextAttr<nscoord>
+ {
+ public:
+ FontSizeTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
+ virtual ~FontSizeTextAttr() { }
+
+ protected:
+
+ // TTextAttr
+ virtual bool GetValueFor(Accessible* aAccessible, nscoord* aValue)
+ override;
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const nscoord& aValue) override;
+
+ private:
+ nsDeviceContext* mDC;
+ };
+
+
+ /**
+ * Class is used for the work with "font-style" text attribute.
+ */
+ class FontStyleTextAttr : public TTextAttr<nscoord>
+ {
+ public:
+ FontStyleTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
+ virtual ~FontStyleTextAttr() { }
+
+ protected:
+
+ // TTextAttr
+ virtual bool GetValueFor(Accessible* aContent, nscoord* aValue)
+ override;
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const nscoord& aValue) override;
+ };
+
+
+ /**
+ * Class is used for the work with "font-weight" text attribute.
+ */
+ class FontWeightTextAttr : public TTextAttr<int32_t>
+ {
+ public:
+ FontWeightTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
+ virtual ~FontWeightTextAttr() { }
+
+ protected:
+
+ // TTextAttr
+ virtual bool GetValueFor(Accessible* aAccessible, int32_t* aValue)
+ override;
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const int32_t& aValue) override;
+
+ private:
+ int32_t GetFontWeight(nsIFrame* aFrame);
+ };
+
+ /**
+ * Class is used for the work with 'auto-generated' text attribute.
+ */
+ class AutoGeneratedTextAttr : public TTextAttr<bool>
+ {
+ public:
+ AutoGeneratedTextAttr(HyperTextAccessible* aHyperTextAcc,
+ Accessible* aAccessible);
+ virtual ~AutoGeneratedTextAttr() { }
+
+ protected:
+ // TextAttr
+ virtual bool GetValueFor(Accessible* aAccessible, bool* aValue)
+ override;
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const bool& aValue) override;
+ };
+
+
+ /**
+ * TextDecorTextAttr class is used for the work with
+ * "text-line-through-style", "text-line-through-color",
+ * "text-underline-style" and "text-underline-color" text attributes.
+ */
+
+ class TextDecorValue
+ {
+ public:
+ TextDecorValue() { }
+ explicit TextDecorValue(nsIFrame* aFrame);
+
+ nscolor Color() const { return mColor; }
+ uint8_t Style() const { return mStyle; }
+
+ bool IsDefined() const
+ { return IsUnderline() || IsLineThrough(); }
+ bool IsUnderline() const
+ { return mLine & NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE; }
+ bool IsLineThrough() const
+ { return mLine & NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH; }
+
+ bool operator ==(const TextDecorValue& aValue)
+ {
+ return mColor == aValue.mColor && mLine == aValue.mLine &&
+ mStyle == aValue.mStyle;
+ }
+ bool operator !=(const TextDecorValue& aValue)
+ { return !(*this == aValue); }
+
+ private:
+ nscolor mColor;
+ uint8_t mLine;
+ uint8_t mStyle;
+ };
+
+ class TextDecorTextAttr : public TTextAttr<TextDecorValue>
+ {
+ public:
+ TextDecorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
+ virtual ~TextDecorTextAttr() { }
+
+ protected:
+
+ // TextAttr
+ virtual bool GetValueFor(Accessible* aAccessible, TextDecorValue* aValue)
+ override;
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const TextDecorValue& aValue) override;
+ };
+
+ /**
+ * Class is used for the work with "text-position" text attribute.
+ */
+
+ enum TextPosValue {
+ eTextPosNone = 0,
+ eTextPosBaseline,
+ eTextPosSub,
+ eTextPosSuper
+ };
+
+ class TextPosTextAttr : public TTextAttr<TextPosValue>
+ {
+ public:
+ TextPosTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
+ virtual ~TextPosTextAttr() { }
+
+ protected:
+
+ // TextAttr
+ virtual bool GetValueFor(Accessible* aAccessible, TextPosValue* aValue)
+ override;
+ virtual void ExposeValue(nsIPersistentProperties* aAttributes,
+ const TextPosValue& aValue) override;
+
+ private:
+ TextPosValue GetTextPosValue(nsIFrame* aFrame) const;
+ };
+
+}; // TextAttrMgr
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/TextRange-inl.h b/accessible/base/TextRange-inl.h
new file mode 100644
index 000000000..15bbaa235
--- /dev/null
+++ b/accessible/base/TextRange-inl.h
@@ -0,0 +1,29 @@
+/* -*- 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_TextRange_inl_h__
+#define mozilla_a11y_TextRange_inl_h__
+
+#include "TextRange.h"
+#include "HyperTextAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+inline Accessible*
+TextRange::Container() const
+{
+ uint32_t pos1 = 0, pos2 = 0;
+ AutoTArray<Accessible*, 30> parents1, parents2;
+ return CommonParent(mStartContainer, mEndContainer,
+ &parents1, &pos1, &parents2, &pos2);
+}
+
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/TextRange.cpp b/accessible/base/TextRange.cpp
new file mode 100644
index 000000000..30474d2fa
--- /dev/null
+++ b/accessible/base/TextRange.cpp
@@ -0,0 +1,380 @@
+/* -*- 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 "TextRange-inl.h"
+
+#include "Accessible-inl.h"
+#include "nsAccUtils.h"
+
+namespace mozilla {
+namespace a11y {
+
+////////////////////////////////////////////////////////////////////////////////
+// TextPoint
+
+bool
+TextPoint::operator <(const TextPoint& aPoint) const
+{
+ if (mContainer == aPoint.mContainer)
+ return mOffset < aPoint.mOffset;
+
+ // Build the chain of parents
+ Accessible* p1 = mContainer;
+ Accessible* p2 = aPoint.mContainer;
+ AutoTArray<Accessible*, 30> parents1, parents2;
+ do {
+ parents1.AppendElement(p1);
+ p1 = p1->Parent();
+ } while (p1);
+ do {
+ parents2.AppendElement(p2);
+ p2 = p2->Parent();
+ } while (p2);
+
+ // Find where the parent chain differs
+ uint32_t pos1 = parents1.Length(), pos2 = parents2.Length();
+ for (uint32_t len = std::min(pos1, pos2); len > 0; --len) {
+ Accessible* child1 = parents1.ElementAt(--pos1);
+ Accessible* child2 = parents2.ElementAt(--pos2);
+ if (child1 != child2)
+ return child1->IndexInParent() < child2->IndexInParent();
+ }
+
+ NS_ERROR("Broken tree?!");
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TextRange
+
+TextRange::TextRange(HyperTextAccessible* aRoot,
+ HyperTextAccessible* aStartContainer, int32_t aStartOffset,
+ HyperTextAccessible* aEndContainer, int32_t aEndOffset) :
+ mRoot(aRoot), mStartContainer(aStartContainer), mEndContainer(aEndContainer),
+ mStartOffset(aStartOffset), mEndOffset(aEndOffset)
+{
+}
+
+void
+TextRange::EmbeddedChildren(nsTArray<Accessible*>* aChildren) const
+{
+ if (mStartContainer == mEndContainer) {
+ int32_t startIdx = mStartContainer->GetChildIndexAtOffset(mStartOffset);
+ int32_t endIdx = mStartContainer->GetChildIndexAtOffset(mEndOffset);
+ for (int32_t idx = startIdx; idx <= endIdx; idx++) {
+ Accessible* child = mStartContainer->GetChildAt(idx);
+ if (!child->IsText()) {
+ aChildren->AppendElement(child);
+ }
+ }
+ return;
+ }
+
+ Accessible* p1 = mStartContainer->GetChildAtOffset(mStartOffset);
+ Accessible* p2 = mEndContainer->GetChildAtOffset(mEndOffset);
+
+ uint32_t pos1 = 0, pos2 = 0;
+ AutoTArray<Accessible*, 30> parents1, parents2;
+ Accessible* container =
+ CommonParent(p1, p2, &parents1, &pos1, &parents2, &pos2);
+
+ // Traverse the tree up to the container and collect embedded objects.
+ for (uint32_t idx = 0; idx < pos1 - 1; idx++) {
+ Accessible* parent = parents1[idx + 1];
+ Accessible* child = parents1[idx];
+ uint32_t childCount = parent->ChildCount();
+ for (uint32_t childIdx = child->IndexInParent(); childIdx < childCount; childIdx++) {
+ Accessible* next = parent->GetChildAt(childIdx);
+ if (!next->IsText()) {
+ aChildren->AppendElement(next);
+ }
+ }
+ }
+
+ // Traverse through direct children in the container.
+ int32_t endIdx = parents2[pos2 - 1]->IndexInParent();
+ int32_t childIdx = parents1[pos1 - 1]->IndexInParent() + 1;
+ for (; childIdx < endIdx; childIdx++) {
+ Accessible* next = container->GetChildAt(childIdx);
+ if (!next->IsText()) {
+ aChildren->AppendElement(next);
+ }
+ }
+
+ // Traverse down from the container to end point.
+ for (int32_t idx = pos2 - 2; idx > 0; idx--) {
+ Accessible* parent = parents2[idx];
+ Accessible* child = parents2[idx - 1];
+ int32_t endIdx = child->IndexInParent();
+ for (int32_t childIdx = 0; childIdx < endIdx; childIdx++) {
+ Accessible* next = parent->GetChildAt(childIdx);
+ if (!next->IsText()) {
+ aChildren->AppendElement(next);
+ }
+ }
+ }
+}
+
+void
+TextRange::Text(nsAString& aText) const
+{
+ Accessible* current = mStartContainer->GetChildAtOffset(mStartOffset);
+ uint32_t startIntlOffset =
+ mStartOffset - mStartContainer->GetChildOffset(current);
+
+ while (current && TextInternal(aText, current, startIntlOffset)) {
+ current = current->Parent();
+ if (!current)
+ break;
+
+ current = current->NextSibling();
+ }
+}
+
+void
+TextRange::Bounds(nsTArray<nsIntRect> aRects) const
+{
+
+}
+
+void
+TextRange::Normalize(ETextUnit aUnit)
+{
+
+}
+
+bool
+TextRange::Crop(Accessible* aContainer)
+{
+ uint32_t boundaryPos = 0, containerPos = 0;
+ AutoTArray<Accessible*, 30> boundaryParents, containerParents;
+
+ // Crop the start boundary.
+ Accessible* container = nullptr;
+ Accessible* boundary = mStartContainer->GetChildAtOffset(mStartOffset);
+ if (boundary != aContainer) {
+ CommonParent(boundary, aContainer, &boundaryParents, &boundaryPos,
+ &containerParents, &containerPos);
+
+ if (boundaryPos == 0) {
+ if (containerPos != 0) {
+ // The container is contained by the start boundary, reduce the range to
+ // the point starting at the container.
+ aContainer->ToTextPoint(mStartContainer.StartAssignment(), &mStartOffset);
+ static_cast<Accessible*>(mStartContainer)->AddRef();
+ }
+ else {
+ // The start boundary and the container are siblings.
+ container = aContainer;
+ }
+ }
+ else if (containerPos != 0) {
+ // The container does not contain the start boundary.
+ boundary = boundaryParents[boundaryPos];
+ container = containerParents[containerPos];
+ }
+
+ if (container) {
+ // If the range start is after the container, then make the range invalid.
+ if (boundary->IndexInParent() > container->IndexInParent()) {
+ return !!(mRoot = nullptr);
+ }
+
+ // If the range starts before the container, then reduce the range to
+ // the point starting at the container.
+ if (boundary->IndexInParent() < container->IndexInParent()) {
+ container->ToTextPoint(mStartContainer.StartAssignment(), &mStartOffset);
+ mStartContainer.get()->AddRef();
+ }
+ }
+
+ boundaryParents.SetLengthAndRetainStorage(0);
+ containerParents.SetLengthAndRetainStorage(0);
+ }
+
+ boundary = mEndContainer->GetChildAtOffset(mEndOffset);
+ if (boundary == aContainer) {
+ return true;
+ }
+
+ // Crop the end boundary.
+ container = nullptr;
+ CommonParent(boundary, aContainer, &boundaryParents, &boundaryPos,
+ &containerParents, &containerPos);
+
+ if (boundaryPos == 0) {
+ if (containerPos != 0) {
+ aContainer->ToTextPoint(mEndContainer.StartAssignment(), &mEndOffset, false);
+ static_cast<Accessible*>(mEndContainer)->AddRef();
+ }
+ else {
+ container = aContainer;
+ }
+ }
+ else if (containerPos != 0) {
+ boundary = boundaryParents[boundaryPos];
+ container = containerParents[containerPos];
+ }
+
+ if (!container) {
+ return true;
+ }
+
+ if (boundary->IndexInParent() < container->IndexInParent()) {
+ return !!(mRoot = nullptr);
+ }
+
+ if (boundary->IndexInParent() > container->IndexInParent()) {
+ container->ToTextPoint(mEndContainer.StartAssignment(), &mEndOffset, false);
+ static_cast<Accessible*>(mEndContainer)->AddRef();
+ }
+
+ return true;
+}
+
+void
+TextRange::FindText(const nsAString& aText, EDirection aDirection,
+ nsCaseTreatment aCaseSensitive, TextRange* aFoundRange) const
+{
+
+}
+
+void
+TextRange::FindAttr(EAttr aAttr, nsIVariant* aValue, EDirection aDirection,
+ TextRange* aFoundRange) const
+{
+
+}
+
+void
+TextRange::AddToSelection() const
+{
+
+}
+
+void
+TextRange::RemoveFromSelection() const
+{
+
+}
+
+void
+TextRange::Select() const
+{
+}
+
+void
+TextRange::ScrollIntoView(EHowToAlign aHow) const
+{
+
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// pivate
+
+void
+TextRange::Set(HyperTextAccessible* aRoot,
+ HyperTextAccessible* aStartContainer, int32_t aStartOffset,
+ HyperTextAccessible* aEndContainer, int32_t aEndOffset)
+{
+ mRoot = aRoot;
+ mStartContainer = aStartContainer;
+ mEndContainer = aEndContainer;
+ mStartOffset = aStartOffset;
+ mEndOffset = aEndOffset;
+}
+
+bool
+TextRange::TextInternal(nsAString& aText, Accessible* aCurrent,
+ uint32_t aStartIntlOffset) const
+{
+ bool moveNext = true;
+ int32_t endIntlOffset = -1;
+ if (aCurrent->Parent() == mEndContainer &&
+ mEndContainer->GetChildAtOffset(mEndOffset) == aCurrent) {
+
+ uint32_t currentStartOffset = mEndContainer->GetChildOffset(aCurrent);
+ endIntlOffset = mEndOffset - currentStartOffset;
+ if (endIntlOffset == 0)
+ return false;
+
+ moveNext = false;
+ }
+
+ if (aCurrent->IsTextLeaf()) {
+ aCurrent->AppendTextTo(aText, aStartIntlOffset,
+ endIntlOffset - aStartIntlOffset);
+ if (!moveNext)
+ return false;
+ }
+
+ Accessible* next = aCurrent->FirstChild();
+ if (next) {
+ if (!TextInternal(aText, next, 0))
+ return false;
+ }
+
+ next = aCurrent->NextSibling();
+ if (next) {
+ if (!TextInternal(aText, next, 0))
+ return false;
+ }
+
+ return moveNext;
+}
+
+
+void
+TextRange::MoveInternal(ETextUnit aUnit, int32_t aCount,
+ HyperTextAccessible& aContainer, int32_t aOffset,
+ HyperTextAccessible* aStopContainer, int32_t aStopOffset)
+{
+
+}
+
+Accessible*
+TextRange::CommonParent(Accessible* aAcc1, Accessible* aAcc2,
+ nsTArray<Accessible*>* aParents1, uint32_t* aPos1,
+ nsTArray<Accessible*>* aParents2, uint32_t* aPos2) const
+{
+ if (aAcc1 == aAcc2) {
+ return aAcc1;
+ }
+
+ MOZ_ASSERT(aParents1->Length() == 0 || aParents2->Length() == 0,
+ "Wrong arguments");
+
+ // Build the chain of parents.
+ Accessible* p1 = aAcc1;
+ Accessible* p2 = aAcc2;
+ do {
+ aParents1->AppendElement(p1);
+ p1 = p1->Parent();
+ } while (p1);
+ do {
+ aParents2->AppendElement(p2);
+ p2 = p2->Parent();
+ } while (p2);
+
+ // Find where the parent chain differs
+ *aPos1 = aParents1->Length();
+ *aPos2 = aParents2->Length();
+ Accessible* parent = nullptr;
+ uint32_t len = 0;
+ for (len = std::min(*aPos1, *aPos2); len > 0; --len) {
+ Accessible* child1 = aParents1->ElementAt(--(*aPos1));
+ Accessible* child2 = aParents2->ElementAt(--(*aPos2));
+ if (child1 != child2)
+ break;
+
+ parent = child1;
+ }
+
+ return parent;
+}
+
+} // namespace a11y
+} // namespace mozilla
diff --git a/accessible/base/TextRange.h b/accessible/base/TextRange.h
new file mode 100644
index 000000000..662b67e42
--- /dev/null
+++ b/accessible/base/TextRange.h
@@ -0,0 +1,270 @@
+/* -*- 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_TextRange_h__
+#define mozilla_a11y_TextRange_h__
+
+#include "mozilla/Move.h"
+#include "nsCaseTreatment.h"
+#include "nsRect.h"
+#include "nsTArray.h"
+
+ class nsIVariant;
+
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+class HyperTextAccessible;
+
+/**
+ * A text point (hyper text + offset), represents a boundary of text range.
+ */
+struct TextPoint final
+{
+ TextPoint(HyperTextAccessible* aContainer, int32_t aOffset) :
+ mContainer(aContainer), mOffset(aOffset) { }
+ TextPoint(const TextPoint& aPoint) :
+ mContainer(aPoint.mContainer), mOffset(aPoint.mOffset) { }
+
+ HyperTextAccessible* mContainer;
+ int32_t mOffset;
+
+ bool operator ==(const TextPoint& aPoint) const
+ { return mContainer == aPoint.mContainer && mOffset == aPoint.mOffset; }
+ bool operator <(const TextPoint& aPoint) const;
+};
+
+/**
+ * Represents a text range within the text control or document.
+ */
+class TextRange final
+{
+public:
+ TextRange(HyperTextAccessible* aRoot,
+ HyperTextAccessible* aStartContainer, int32_t aStartOffset,
+ HyperTextAccessible* aEndContainer, int32_t aEndOffset);
+ TextRange() {}
+ TextRange(TextRange&& aRange) :
+ mRoot(mozilla::Move(aRange.mRoot)),
+ mStartContainer(mozilla::Move(aRange.mStartContainer)),
+ mEndContainer(mozilla::Move(aRange.mEndContainer)),
+ mStartOffset(aRange.mStartOffset), mEndOffset(aRange.mEndOffset) {}
+
+ TextRange& operator= (TextRange&& aRange)
+ {
+ mRoot = mozilla::Move(aRange.mRoot);
+ mStartContainer = mozilla::Move(aRange.mStartContainer);
+ mEndContainer = mozilla::Move(aRange.mEndContainer);
+ mStartOffset = aRange.mStartOffset;
+ mEndOffset = aRange.mEndOffset;
+ return *this;
+ }
+
+ HyperTextAccessible* StartContainer() const { return mStartContainer; }
+ int32_t StartOffset() const { return mStartOffset; }
+ HyperTextAccessible* EndContainer() const { return mEndContainer; }
+ int32_t EndOffset() const { return mEndOffset; }
+
+ bool operator ==(const TextRange& aRange) const
+ {
+ return mStartContainer == aRange.mStartContainer &&
+ mStartOffset == aRange.mStartOffset &&
+ mEndContainer == aRange.mEndContainer && mEndOffset == aRange.mEndOffset;
+ }
+
+ TextPoint StartPoint() const { return TextPoint(mStartContainer, mStartOffset); }
+ TextPoint EndPoint() const { return TextPoint(mEndContainer, mEndOffset); }
+
+ /**
+ * Return a container containing both start and end points.
+ */
+ Accessible* Container() const;
+
+ /**
+ * Return a list of embedded objects enclosed by the text range (includes
+ * partially overlapped objects).
+ */
+ void EmbeddedChildren(nsTArray<Accessible*>* aChildren) const;
+
+ /**
+ * Return text enclosed by the range.
+ */
+ void Text(nsAString& aText) const;
+
+ /**
+ * Return list of bounding rects of the text range by lines.
+ */
+ void Bounds(nsTArray<nsIntRect> aRects) const;
+
+ enum ETextUnit {
+ eFormat,
+ eWord,
+ eLine,
+ eParagraph,
+ ePage,
+ eDocument
+ };
+
+ /**
+ * Move the range or its points on specified amount of given units.
+ */
+ void Move(ETextUnit aUnit, int32_t aCount)
+ {
+ MoveEnd(aUnit, aCount);
+ MoveStart(aUnit, aCount);
+ }
+ void MoveStart(ETextUnit aUnit, int32_t aCount)
+ {
+ MoveInternal(aUnit, aCount, *mStartContainer, mStartOffset,
+ mEndContainer, mEndOffset);
+ }
+ void MoveEnd(ETextUnit aUnit, int32_t aCount)
+ { MoveInternal(aUnit, aCount, *mEndContainer, mEndOffset); }
+
+ /**
+ * Move the range points to the closest unit boundaries.
+ */
+ void Normalize(ETextUnit aUnit);
+
+ /**
+ * Crops the range if it overlaps the given accessible element boundaries,
+ * returns true if the range was cropped successfully.
+ */
+ bool Crop(Accessible* aContainer);
+
+ enum EDirection {
+ eBackward,
+ eForward
+ };
+
+ /**
+ * Return range enclosing the found text.
+ */
+ void FindText(const nsAString& aText, EDirection aDirection,
+ nsCaseTreatment aCaseSensitive, TextRange* aFoundRange) const;
+
+ enum EAttr {
+ eAnimationStyleAttr,
+ eAnnotationObjectsAttr,
+ eAnnotationTypesAttr,
+ eBackgroundColorAttr,
+ eBulletStyleAttr,
+ eCapStyleAttr,
+ eCaretBidiModeAttr,
+ eCaretPositionAttr,
+ eCultureAttr,
+ eFontNameAttr,
+ eFontSizeAttr,
+ eFontWeightAttr,
+ eForegroundColorAttr,
+ eHorizontalTextAlignmentAttr,
+ eIndentationFirstLineAttr,
+ eIndentationLeadingAttr,
+ eIndentationTrailingAttr,
+ eIsActiveAttr,
+ eIsHiddenAttr,
+ eIsItalicAttr,
+ eIsReadOnlyAttr,
+ eIsSubscriptAttr,
+ eIsSuperscriptAttr,
+ eLinkAttr,
+ eMarginBottomAttr,
+ eMarginLeadingAttr,
+ eMarginTopAttr,
+ eMarginTrailingAttr,
+ eOutlineStylesAttr,
+ eOverlineColorAttr,
+ eOverlineStyleAttr,
+ eSelectionActiveEndAttr,
+ eStrikethroughColorAttr,
+ eStrikethroughStyleAttr,
+ eStyleIdAttr,
+ eStyleNameAttr,
+ eTabsAttr,
+ eTextFlowDirectionsAttr,
+ eUnderlineColorAttr,
+ eUnderlineStyleAttr
+ };
+
+ /**
+ * Return range enclosing text having requested attribute.
+ */
+ void FindAttr(EAttr aAttr, nsIVariant* aValue, EDirection aDirection,
+ TextRange* aFoundRange) const;
+
+ /**
+ * Add/remove the text range from selection.
+ */
+ void AddToSelection() const;
+ void RemoveFromSelection() const;
+ void Select() const;
+
+ /**
+ * Scroll the text range into view.
+ */
+ enum EHowToAlign {
+ eAlignToTop,
+ eAlignToBottom
+ };
+ void ScrollIntoView(EHowToAlign aHow) const;
+
+ /**
+ * Return true if this TextRange object represents an actual range of text.
+ */
+ bool IsValid() const { return mRoot; }
+
+ void SetStartPoint(HyperTextAccessible* aContainer, int32_t aOffset)
+ { mStartContainer = aContainer; mStartOffset = aOffset; }
+ void SetEndPoint(HyperTextAccessible* aContainer, int32_t aOffset)
+ { mStartContainer = aContainer; mStartOffset = aOffset; }
+
+private:
+ TextRange(const TextRange& aRange) = delete;
+ TextRange& operator=(const TextRange& aRange) = delete;
+
+ friend class HyperTextAccessible;
+ friend class xpcAccessibleTextRange;
+
+ void Set(HyperTextAccessible* aRoot,
+ HyperTextAccessible* aStartContainer, int32_t aStartOffset,
+ HyperTextAccessible* aEndContainer, int32_t aEndOffset);
+
+ /**
+ * Text() method helper.
+ * @param aText [in,out] calculated text
+ * @param aCurrent [in] currently traversed node
+ * @param aStartIntlOffset [in] start offset if current node is a text node
+ * @return true if calculation is not finished yet
+ */
+ bool TextInternal(nsAString& aText, Accessible* aCurrent,
+ uint32_t aStartIntlOffset) const;
+
+ void MoveInternal(ETextUnit aUnit, int32_t aCount,
+ HyperTextAccessible& aContainer, int32_t aOffset,
+ HyperTextAccessible* aStopContainer = nullptr,
+ int32_t aStopOffset = 0);
+
+ /**
+ * A helper method returning a common parent for two given accessible
+ * elements.
+ */
+ Accessible* CommonParent(Accessible* aAcc1, Accessible* aAcc2,
+ nsTArray<Accessible*>* aParents1, uint32_t* aPos1,
+ nsTArray<Accessible*>* aParents2, uint32_t* aPos2) const;
+
+ RefPtr<HyperTextAccessible> mRoot;
+ RefPtr<HyperTextAccessible> mStartContainer;
+ RefPtr<HyperTextAccessible> mEndContainer;
+ int32_t mStartOffset;
+ int32_t mEndOffset;
+};
+
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/TextUpdater.cpp b/accessible/base/TextUpdater.cpp
new file mode 100644
index 000000000..75cbbe7e1
--- /dev/null
+++ b/accessible/base/TextUpdater.cpp
@@ -0,0 +1,202 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "TextUpdater.h"
+
+#include "Accessible-inl.h"
+#include "DocAccessible-inl.h"
+#include "TextLeafAccessible.h"
+#include <algorithm>
+
+using namespace mozilla::a11y;
+
+void
+TextUpdater::Run(DocAccessible* aDocument, TextLeafAccessible* aTextLeaf,
+ const nsAString& aNewText)
+{
+ NS_ASSERTION(aTextLeaf, "No text leaf accessible?");
+
+ const nsString& oldText = aTextLeaf->Text();
+ uint32_t oldLen = oldText.Length(), newLen = aNewText.Length();
+ uint32_t minLen = std::min(oldLen, newLen);
+
+ // Skip coinciding begin substrings.
+ uint32_t skipStart = 0;
+ for (; skipStart < minLen; skipStart++) {
+ if (aNewText[skipStart] != oldText[skipStart])
+ break;
+ }
+
+ // The text was changed. Do update.
+ if (skipStart != minLen || oldLen != newLen) {
+ TextUpdater updater(aDocument, aTextLeaf);
+ updater.DoUpdate(aNewText, oldText, skipStart);
+ }
+}
+
+void
+TextUpdater::DoUpdate(const nsAString& aNewText, const nsAString& aOldText,
+ uint32_t aSkipStart)
+{
+ Accessible* parent = mTextLeaf->Parent();
+ if (!parent)
+ return;
+
+ mHyperText = parent->AsHyperText();
+ if (!mHyperText) {
+ NS_ERROR("Text leaf parent is not hypertext!");
+ return;
+ }
+
+ // Get the text leaf accessible offset and invalidate cached offsets after it.
+ mTextOffset = mHyperText->GetChildOffset(mTextLeaf, true);
+ NS_ASSERTION(mTextOffset != -1,
+ "Text leaf hasn't offset within hyper text!");
+
+ uint32_t oldLen = aOldText.Length(), newLen = aNewText.Length();
+ uint32_t minLen = std::min(oldLen, newLen);
+
+ // Trim coinciding substrings from the end.
+ uint32_t skipEnd = 0;
+ while (minLen - skipEnd > aSkipStart &&
+ aNewText[newLen - skipEnd - 1] == aOldText[oldLen - skipEnd - 1]) {
+ skipEnd++;
+ }
+
+ uint32_t strLen1 = oldLen - aSkipStart - skipEnd;
+ uint32_t strLen2 = newLen - aSkipStart - skipEnd;
+
+ const nsAString& str1 = Substring(aOldText, aSkipStart, strLen1);
+ const nsAString& str2 = Substring(aNewText, aSkipStart, strLen2);
+
+ // Increase offset of the text leaf on skipped characters amount.
+ mTextOffset += aSkipStart;
+
+ // It could be single insertion or removal or the case of long strings. Do not
+ // calculate the difference between long strings and prefer to fire pair of
+ // insert/remove events as the old string was replaced on the new one.
+ if (strLen1 == 0 || strLen2 == 0 ||
+ strLen1 > kMaxStrLen || strLen2 > kMaxStrLen) {
+ if (strLen1 > 0) {
+ // Fire text change event for removal.
+ RefPtr<AccEvent> textRemoveEvent =
+ new AccTextChangeEvent(mHyperText, mTextOffset, str1, false);
+ mDocument->FireDelayedEvent(textRemoveEvent);
+ }
+
+ if (strLen2 > 0) {
+ // Fire text change event for insertion.
+ RefPtr<AccEvent> textInsertEvent =
+ new AccTextChangeEvent(mHyperText, mTextOffset, str2, true);
+ mDocument->FireDelayedEvent(textInsertEvent);
+ }
+
+ mDocument->MaybeNotifyOfValueChange(mHyperText);
+
+ // Update the text.
+ mTextLeaf->SetText(aNewText);
+ return;
+ }
+
+ // Otherwise find the difference between strings and fire events.
+ // Note: we can skip initial and final coinciding characters since they don't
+ // affect the Levenshtein distance.
+
+ // Compute the flat structured matrix need to compute the difference.
+ uint32_t len1 = strLen1 + 1, len2 = strLen2 + 1;
+ uint32_t* entries = new uint32_t[len1 * len2];
+
+ for (uint32_t colIdx = 0; colIdx < len1; colIdx++)
+ entries[colIdx] = colIdx;
+
+ uint32_t* row = entries;
+ for (uint32_t rowIdx = 1; rowIdx < len2; rowIdx++) {
+ uint32_t* prevRow = row;
+ row += len1;
+ row[0] = rowIdx;
+ for (uint32_t colIdx = 1; colIdx < len1; colIdx++) {
+ if (str1[colIdx - 1] != str2[rowIdx - 1]) {
+ uint32_t left = row[colIdx - 1];
+ uint32_t up = prevRow[colIdx];
+ uint32_t upleft = prevRow[colIdx - 1];
+ row[colIdx] = std::min(upleft, std::min(left, up)) + 1;
+ } else {
+ row[colIdx] = prevRow[colIdx - 1];
+ }
+ }
+ }
+
+ // Compute events based on the difference.
+ nsTArray<RefPtr<AccEvent> > events;
+ ComputeTextChangeEvents(str1, str2, entries, events);
+
+ delete [] entries;
+
+ // Fire events.
+ for (int32_t idx = events.Length() - 1; idx >= 0; idx--)
+ mDocument->FireDelayedEvent(events[idx]);
+
+ mDocument->MaybeNotifyOfValueChange(mHyperText);
+
+ // Update the text.
+ mTextLeaf->SetText(aNewText);
+}
+
+void
+TextUpdater::ComputeTextChangeEvents(const nsAString& aStr1,
+ const nsAString& aStr2,
+ uint32_t* aEntries,
+ nsTArray<RefPtr<AccEvent> >& aEvents)
+{
+ int32_t colIdx = aStr1.Length(), rowIdx = aStr2.Length();
+
+ // Point at which strings last matched.
+ int32_t colEnd = colIdx;
+ int32_t rowEnd = rowIdx;
+
+ int32_t colLen = colEnd + 1;
+ uint32_t* row = aEntries + rowIdx * colLen;
+ uint32_t dist = row[colIdx]; // current Levenshtein distance
+ while (rowIdx && colIdx) { // stop when we can't move diagonally
+ if (aStr1[colIdx - 1] == aStr2[rowIdx - 1]) { // match
+ if (rowIdx < rowEnd) { // deal with any pending insertion
+ FireInsertEvent(Substring(aStr2, rowIdx, rowEnd - rowIdx),
+ rowIdx, aEvents);
+ }
+ if (colIdx < colEnd) { // deal with any pending deletion
+ FireDeleteEvent(Substring(aStr1, colIdx, colEnd - colIdx),
+ rowIdx, aEvents);
+ }
+
+ colEnd = --colIdx; // reset the match point
+ rowEnd = --rowIdx;
+ row -= colLen;
+ continue;
+ }
+ --dist;
+ if (dist == row[colIdx - 1 - colLen]) { // substitution
+ --colIdx;
+ --rowIdx;
+ row -= colLen;
+ continue;
+ }
+ if (dist == row[colIdx - colLen]) { // insertion
+ --rowIdx;
+ row -= colLen;
+ continue;
+ }
+ if (dist == row[colIdx - 1]) { // deletion
+ --colIdx;
+ continue;
+ }
+ NS_NOTREACHED("huh?");
+ return;
+ }
+
+ if (rowEnd)
+ FireInsertEvent(Substring(aStr2, 0, rowEnd), 0, aEvents);
+ if (colEnd)
+ FireDeleteEvent(Substring(aStr1, 0, colEnd), 0, aEvents);
+}
diff --git a/accessible/base/TextUpdater.h b/accessible/base/TextUpdater.h
new file mode 100644
index 000000000..06df3890d
--- /dev/null
+++ b/accessible/base/TextUpdater.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_TextUpdater_h__
+#define mozilla_a11y_TextUpdater_h__
+
+#include "AccEvent.h"
+#include "HyperTextAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Used to find a difference between old and new text and fire text change
+ * events.
+ */
+class TextUpdater
+{
+public:
+ /**
+ * Start text of the text leaf update.
+ */
+ static void Run(DocAccessible* aDocument, TextLeafAccessible* aTextLeaf,
+ const nsAString& aNewText);
+
+private:
+ TextUpdater(DocAccessible* aDocument, TextLeafAccessible* aTextLeaf) :
+ mDocument(aDocument), mTextLeaf(aTextLeaf), mHyperText(nullptr),
+ mTextOffset(-1) { }
+
+ ~TextUpdater()
+ { mDocument = nullptr; mTextLeaf = nullptr; mHyperText = nullptr; }
+
+ /**
+ * Update text of the text leaf accessible, fire text change and value change
+ * (if applicable) events for its container hypertext accessible.
+ */
+ void DoUpdate(const nsAString& aNewText, const nsAString& aOldText,
+ uint32_t aSkipStart);
+
+private:
+ TextUpdater();
+ TextUpdater(const TextUpdater&);
+ TextUpdater& operator=(const TextUpdater&);
+
+ /**
+ * Fire text change events based on difference between strings.
+ */
+ void ComputeTextChangeEvents(const nsAString& aStr1,
+ const nsAString& aStr2,
+ uint32_t* aEntries,
+ nsTArray<RefPtr<AccEvent> >& aEvents);
+
+ /**
+ * Helper to create text change events for inserted text.
+ */
+ inline void FireInsertEvent(const nsAString& aText, uint32_t aAddlOffset,
+ nsTArray<RefPtr<AccEvent> >& aEvents)
+ {
+ RefPtr<AccEvent> event =
+ new AccTextChangeEvent(mHyperText, mTextOffset + aAddlOffset,
+ aText, true);
+ aEvents.AppendElement(event);
+ }
+
+ /**
+ * Helper to create text change events for removed text.
+ */
+ inline void FireDeleteEvent(const nsAString& aText, uint32_t aAddlOffset,
+ nsTArray<RefPtr<AccEvent> >& aEvents)
+ {
+ RefPtr<AccEvent> event =
+ new AccTextChangeEvent(mHyperText, mTextOffset + aAddlOffset,
+ aText, false);
+ aEvents.AppendElement(event);
+ }
+
+ /**
+ * The constant used to skip string difference calculation in case of long
+ * strings.
+ */
+ const static uint32_t kMaxStrLen = 1 << 6;
+
+private:
+ DocAccessible* mDocument;
+ TextLeafAccessible* mTextLeaf;
+ HyperTextAccessible* mHyperText;
+ int32_t mTextOffset;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/TreeWalker.cpp b/accessible/base/TreeWalker.cpp
new file mode 100644
index 000000000..8c04b5d6f
--- /dev/null
+++ b/accessible/base/TreeWalker.cpp
@@ -0,0 +1,325 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "TreeWalker.h"
+
+#include "Accessible.h"
+#include "AccIterator.h"
+#include "nsAccessibilityService.h"
+#include "DocAccessible.h"
+
+#include "mozilla/dom/ChildIterator.h"
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// TreeWalker
+////////////////////////////////////////////////////////////////////////////////
+
+TreeWalker::
+ TreeWalker(Accessible* aContext) :
+ mDoc(aContext->Document()), mContext(aContext), mAnchorNode(nullptr),
+ mARIAOwnsIdx(0),
+ mChildFilter(nsIContent::eSkipPlaceholderContent), mFlags(0),
+ mPhase(eAtStart)
+{
+ mChildFilter |= mContext->NoXBLKids() ?
+ nsIContent::eAllButXBL : nsIContent::eAllChildren;
+
+ mAnchorNode = mContext->IsDoc() ?
+ mDoc->DocumentNode()->GetRootElement() : mContext->GetContent();
+
+ MOZ_COUNT_CTOR(TreeWalker);
+}
+
+TreeWalker::
+ TreeWalker(Accessible* aContext, nsIContent* aAnchorNode, uint32_t aFlags) :
+ mDoc(aContext->Document()), mContext(aContext), mAnchorNode(aAnchorNode),
+ mARIAOwnsIdx(0),
+ mChildFilter(nsIContent::eSkipPlaceholderContent), mFlags(aFlags),
+ mPhase(eAtStart)
+{
+ MOZ_ASSERT(mFlags & eWalkCache, "This constructor cannot be used for tree creation");
+ MOZ_ASSERT(aAnchorNode, "No anchor node for the accessible tree walker");
+
+ mChildFilter |= mContext->NoXBLKids() ?
+ nsIContent::eAllButXBL : nsIContent::eAllChildren;
+
+ MOZ_COUNT_CTOR(TreeWalker);
+}
+
+TreeWalker::~TreeWalker()
+{
+ MOZ_COUNT_DTOR(TreeWalker);
+}
+
+Accessible*
+TreeWalker::Scope(nsIContent* aAnchorNode)
+{
+ Reset();
+
+ mAnchorNode = aAnchorNode;
+
+ bool skipSubtree = false;
+ Accessible* acc = AccessibleFor(aAnchorNode, 0, &skipSubtree);
+ if (acc) {
+ mPhase = eAtEnd;
+ return acc;
+ }
+
+ return skipSubtree ? nullptr : Next();
+}
+
+bool
+TreeWalker::Seek(nsIContent* aChildNode)
+{
+ MOZ_ASSERT(aChildNode, "Child cannot be null");
+
+ Reset();
+
+ if (mAnchorNode == aChildNode) {
+ return true;
+ }
+
+ nsIContent* childNode = nullptr;
+ nsINode* parentNode = aChildNode;
+ do {
+ childNode = parentNode->AsContent();
+ parentNode = childNode->HasFlag(NODE_MAY_BE_IN_BINDING_MNGR) &&
+ (mChildFilter & nsIContent::eAllButXBL) ?
+ childNode->GetParentNode() : childNode->GetFlattenedTreeParent();
+
+ if (!parentNode || !parentNode->IsElement()) {
+ return false;
+ }
+
+ // If ARIA owned child.
+ Accessible* child = mDoc->GetAccessible(childNode);
+ if (child && child->IsRelocated()) {
+ if (child->Parent() != mContext) {
+ return false;
+ }
+
+ Accessible* ownedChild = nullptr;
+ while ((ownedChild = mDoc->ARIAOwnedAt(mContext, mARIAOwnsIdx++)) &&
+ ownedChild != child);
+
+ MOZ_ASSERT(ownedChild, "A child has to be in ARIA owned elements");
+ mPhase = eAtARIAOwns;
+ return true;
+ }
+
+ // Look in DOM.
+ dom::AllChildrenIterator* iter = PrependState(parentNode->AsElement(), true);
+ if (!iter->Seek(childNode)) {
+ return false;
+ }
+
+ if (parentNode == mAnchorNode) {
+ mPhase = eAtDOM;
+ return true;
+ }
+ } while (true);
+
+ return false;
+}
+
+Accessible*
+TreeWalker::Next()
+{
+ if (mStateStack.IsEmpty()) {
+ if (mPhase == eAtEnd) {
+ return nullptr;
+ }
+
+ if (mPhase == eAtDOM || mPhase == eAtARIAOwns) {
+ mPhase = eAtARIAOwns;
+ Accessible* child = mDoc->ARIAOwnedAt(mContext, mARIAOwnsIdx);
+ if (child) {
+ mARIAOwnsIdx++;
+ return child;
+ }
+ mPhase = eAtEnd;
+ return nullptr;
+ }
+
+ if (!mAnchorNode) {
+ mPhase = eAtEnd;
+ return nullptr;
+ }
+
+ mPhase = eAtDOM;
+ PushState(mAnchorNode, true);
+ }
+
+ dom::AllChildrenIterator* top = &mStateStack[mStateStack.Length() - 1];
+ while (top) {
+ while (nsIContent* childNode = top->GetNextChild()) {
+ bool skipSubtree = false;
+ Accessible* child = AccessibleFor(childNode, mFlags, &skipSubtree);
+ if (child) {
+ return child;
+ }
+
+ // Walk down the subtree if allowed.
+ if (!skipSubtree && childNode->IsElement()) {
+ top = PushState(childNode, true);
+ }
+ }
+ top = PopState();
+ }
+
+ // If we traversed the whole subtree of the anchor node. Move to next node
+ // relative anchor node within the context subtree if asked.
+ if (mFlags != eWalkContextTree) {
+ // eWalkCache flag presence indicates that the search is scoped to the
+ // anchor (no ARIA owns stuff).
+ if (mFlags & eWalkCache) {
+ mPhase = eAtEnd;
+ return nullptr;
+ }
+ return Next();
+ }
+
+ nsINode* contextNode = mContext->GetNode();
+ while (mAnchorNode != contextNode) {
+ nsINode* parentNode = mAnchorNode->GetFlattenedTreeParent();
+ if (!parentNode || !parentNode->IsElement())
+ return nullptr;
+
+ nsIContent* parent = parentNode->AsElement();
+ top = PushState(parent, true);
+ if (top->Seek(mAnchorNode)) {
+ mAnchorNode = parent;
+ return Next();
+ }
+
+ // XXX We really should never get here, it means we're trying to find an
+ // accessible for a dom node where iterating over its parent's children
+ // doesn't return it. However this sometimes happens when we're asked for
+ // the nearest accessible to place holder content which we ignore.
+ mAnchorNode = parent;
+ }
+
+ return Next();
+}
+
+Accessible*
+TreeWalker::Prev()
+{
+ if (mStateStack.IsEmpty()) {
+ if (mPhase == eAtStart || mPhase == eAtDOM) {
+ mPhase = eAtStart;
+ return nullptr;
+ }
+
+ if (mPhase == eAtEnd) {
+ mARIAOwnsIdx = mDoc->ARIAOwnedCount(mContext);
+ mPhase = eAtARIAOwns;
+ }
+
+ if (mPhase == eAtARIAOwns) {
+ if (mARIAOwnsIdx > 0) {
+ return mDoc->ARIAOwnedAt(mContext, --mARIAOwnsIdx);
+ }
+
+ if (!mAnchorNode) {
+ mPhase = eAtStart;
+ return nullptr;
+ }
+
+ mPhase = eAtDOM;
+ PushState(mAnchorNode, false);
+ }
+ }
+
+ dom::AllChildrenIterator* top = &mStateStack[mStateStack.Length() - 1];
+ while (top) {
+ while (nsIContent* childNode = top->GetPreviousChild()) {
+ // No accessible creation on the way back.
+ bool skipSubtree = false;
+ Accessible* child = AccessibleFor(childNode, eWalkCache, &skipSubtree);
+ if (child) {
+ return child;
+ }
+
+ // Walk down into subtree to find accessibles.
+ if (!skipSubtree && childNode->IsElement()) {
+ top = PushState(childNode, false);
+ }
+ }
+ top = PopState();
+ }
+
+ // Move to a previous node relative the anchor node within the context
+ // subtree if asked.
+ if (mFlags != eWalkContextTree) {
+ mPhase = eAtStart;
+ return nullptr;
+ }
+
+ nsINode* contextNode = mContext->GetNode();
+ while (mAnchorNode != contextNode) {
+ nsINode* parentNode = mAnchorNode->GetFlattenedTreeParent();
+ if (!parentNode || !parentNode->IsElement()) {
+ return nullptr;
+ }
+
+ nsIContent* parent = parentNode->AsElement();
+ top = PushState(parent, true);
+ if (top->Seek(mAnchorNode)) {
+ mAnchorNode = parent;
+ return Prev();
+ }
+
+ mAnchorNode = parent;
+ }
+
+ mPhase = eAtStart;
+ return nullptr;
+}
+
+Accessible*
+TreeWalker::AccessibleFor(nsIContent* aNode, uint32_t aFlags, bool* aSkipSubtree)
+{
+ // Ignore the accessible and its subtree if it was repositioned by means
+ // of aria-owns.
+ Accessible* child = mDoc->GetAccessible(aNode);
+ if (child) {
+ if (child->IsRelocated()) {
+ *aSkipSubtree = true;
+ return nullptr;
+ }
+ return child;
+ }
+
+ // Create an accessible if allowed.
+ if (!(aFlags & eWalkCache) && mContext->IsAcceptableChild(aNode)) {
+ // We may have ARIA owned element in the dependent attributes map, but the
+ // element may be not allowed for this ARIA owns relation, if the relation
+ // crosses out XBL anonymous content boundaries. In this case we won't
+ // create an accessible object for it, when aria-owns is processed, which
+ // may make the element subtree inaccessible. To avoid that let's create
+ // an accessible object now, and later, if allowed, move it in the tree,
+ // when aria-owns relation is processed.
+ if (mDoc->RelocateARIAOwnedIfNeeded(aNode) && !aNode->IsXULElement()) {
+ *aSkipSubtree = true;
+ return nullptr;
+ }
+ return GetAccService()->CreateAccessible(aNode, mContext, aSkipSubtree);
+ }
+
+ return nullptr;
+}
+
+dom::AllChildrenIterator*
+TreeWalker::PopState()
+{
+ size_t length = mStateStack.Length();
+ mStateStack.RemoveElementAt(length - 1);
+ return mStateStack.IsEmpty() ? nullptr : &mStateStack.LastElement();
+}
diff --git a/accessible/base/TreeWalker.h b/accessible/base/TreeWalker.h
new file mode 100644
index 000000000..377a5e3b8
--- /dev/null
+++ b/accessible/base/TreeWalker.h
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_TreeWalker_h_
+#define mozilla_a11y_TreeWalker_h_
+
+#include "mozilla/Attributes.h"
+#include <stdint.h>
+#include "mozilla/dom/ChildIterator.h"
+#include "nsCOMPtr.h"
+
+class nsIContent;
+
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+class DocAccessible;
+
+/**
+ * This class is used to walk the DOM tree to create accessible tree.
+ */
+class TreeWalker final
+{
+public:
+ enum {
+ // used to walk the existing tree of the given node
+ eWalkCache = 1,
+ // used to walk the context tree starting from given node
+ eWalkContextTree = 2 | eWalkCache
+ };
+
+ /**
+ * Used to navigate and create if needed the accessible children.
+ */
+ explicit TreeWalker(Accessible* aContext);
+
+ /**
+ * Used to navigate the accessible children relative to the anchor.
+ *
+ * @param aContext [in] container accessible for the given node, used to
+ * define accessible context
+ * @param aAnchorNode [in] the node the search will be prepared relative to
+ * @param aFlags [in] flags (see enum above)
+ */
+ TreeWalker(Accessible* aContext, nsIContent* aAnchorNode, uint32_t aFlags = eWalkCache);
+
+ ~TreeWalker();
+
+ /**
+ * Resets the walker state, and sets the given node as an anchor. Returns a
+ * first accessible element within the node including the node itself.
+ */
+ Accessible* Scope(nsIContent* aAnchorNode);
+
+ /**
+ * Resets the walker state.
+ */
+ void Reset()
+ {
+ mPhase = eAtStart;
+ mStateStack.Clear();
+ mARIAOwnsIdx = 0;
+ }
+
+ /**
+ * Sets the walker state to the given child node if it's within the anchor.
+ */
+ bool Seek(nsIContent* aChildNode);
+
+ /**
+ * Return the next/prev accessible.
+ *
+ * @note Returned accessible is bound to the document, if the accessible is
+ * rejected during tree creation then the caller should be unbind it
+ * from the document.
+ */
+ Accessible* Next();
+ Accessible* Prev();
+
+ Accessible* Context() const { return mContext; }
+ DocAccessible* Document() const { return mDoc; }
+
+private:
+ TreeWalker();
+ TreeWalker(const TreeWalker&);
+ TreeWalker& operator =(const TreeWalker&);
+
+ /**
+ * Return an accessible for the given node if any.
+ */
+ Accessible* AccessibleFor(nsIContent* aNode, uint32_t aFlags,
+ bool* aSkipSubtree);
+
+ /**
+ * Create new state for the given node and push it on top of stack / at bottom
+ * of stack.
+ *
+ * @note State stack is used to navigate up/down the DOM subtree during
+ * accessible children search.
+ */
+ dom::AllChildrenIterator* PushState(nsIContent* aContent,
+ bool aStartAtBeginning)
+ {
+ return mStateStack.AppendElement(
+ dom::AllChildrenIterator(aContent, mChildFilter, aStartAtBeginning));
+ }
+ dom::AllChildrenIterator* PrependState(nsIContent* aContent,
+ bool aStartAtBeginning)
+ {
+ return mStateStack.InsertElementAt(0,
+ dom::AllChildrenIterator(aContent, mChildFilter, aStartAtBeginning));
+ }
+
+ /**
+ * Pop state from stack.
+ */
+ dom::AllChildrenIterator* PopState();
+
+ DocAccessible* mDoc;
+ Accessible* mContext;
+ nsIContent* mAnchorNode;
+
+ AutoTArray<dom::AllChildrenIterator, 20> mStateStack;
+ uint32_t mARIAOwnsIdx;
+
+ int32_t mChildFilter;
+ uint32_t mFlags;
+
+ enum Phase {
+ eAtStart,
+ eAtDOM,
+ eAtARIAOwns,
+ eAtEnd
+ };
+ Phase mPhase;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_TreeWalker_h_
diff --git a/accessible/base/moz.build b/accessible/base/moz.build
new file mode 100644
index 000000000..dcccc4b54
--- /dev/null
+++ b/accessible/base/moz.build
@@ -0,0 +1,114 @@
+# -*- 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 += [
+ 'AccEvent.h',
+ 'nsAccessibilityService.h'
+]
+
+EXPORTS.mozilla.a11y += [
+ 'AccTypes.h',
+ 'DocManager.h',
+ 'FocusManager.h',
+ 'Platform.h',
+ 'RelationType.h',
+ 'Role.h',
+ 'SelectionManager.h',
+ 'States.h',
+]
+
+if CONFIG['MOZ_DEBUG']:
+ EXPORTS.mozilla.a11y += [
+ 'Logging.h',
+ ]
+
+UNIFIED_SOURCES += [
+ 'AccessibleOrProxy.cpp',
+ 'AccEvent.cpp',
+ 'AccGroupInfo.cpp',
+ 'AccIterator.cpp',
+ 'ARIAMap.cpp',
+ 'ARIAStateMap.cpp',
+ 'Asserts.cpp',
+ 'DocManager.cpp',
+ 'EmbeddedObjCollector.cpp',
+ 'EventQueue.cpp',
+ 'EventTree.cpp',
+ 'Filters.cpp',
+ 'FocusManager.cpp',
+ 'NotificationController.cpp',
+ 'nsAccessibilityService.cpp',
+ 'nsAccessiblePivot.cpp',
+ 'nsAccUtils.cpp',
+ 'nsCoreUtils.cpp',
+ 'nsEventShell.cpp',
+ 'nsTextEquivUtils.cpp',
+ 'SelectionManager.cpp',
+ 'StyleInfo.cpp',
+ 'TextAttrs.cpp',
+ 'TextRange.cpp',
+ 'TextUpdater.cpp',
+ 'TreeWalker.cpp',
+]
+
+if CONFIG['A11Y_LOG']:
+ UNIFIED_SOURCES += [
+ 'Logging.cpp',
+ ]
+
+LOCAL_INCLUDES += [
+ '/accessible/generic',
+ '/accessible/html',
+ '/accessible/ipc',
+]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ LOCAL_INCLUDES += [
+ '/accessible/ipc/win',
+ ]
+else:
+ LOCAL_INCLUDES += [
+ '/accessible/ipc/other',
+ ]
+
+LOCAL_INCLUDES += [
+ '/accessible/xpcom',
+ '/accessible/xul',
+ '/dom/base',
+ '/dom/xbl',
+ '/ipc/chromium/src',
+ '/layout/generic',
+ '/layout/style',
+ '/layout/svg',
+ '/layout/xul',
+ '/layout/xul/tree/',
+]
+
+if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ LOCAL_INCLUDES += [
+ '/accessible/atk',
+ ]
+ CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ LOCAL_INCLUDES += [
+ '/accessible/windows/ia2',
+ '/accessible/windows/msaa',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ LOCAL_INCLUDES += [
+ '/accessible/mac',
+ ]
+else:
+ LOCAL_INCLUDES += [
+ '/accessible/other',
+ ]
+
+FINAL_LIBRARY = 'xul'
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/accessible/base/nsAccCache.h b/accessible/base/nsAccCache.h
new file mode 100644
index 000000000..cb39accde
--- /dev/null
+++ b/accessible/base/nsAccCache.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 _nsAccCache_H_
+#define _nsAccCache_H_
+
+#include "xpcAccessibleDocument.h"
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible cache utils
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+void
+UnbindCacheEntriesFromDocument(
+ nsRefPtrHashtable<nsPtrHashKey<const void>, T>& aCache)
+{
+ for (auto iter = aCache.Iter(); !iter.Done(); iter.Next()) {
+ T* accessible = iter.Data();
+ MOZ_ASSERT(accessible && !accessible->IsDefunct());
+ accessible->Document()->UnbindFromDocument(accessible);
+ iter.Remove();
+ }
+}
+
+#endif
diff --git a/accessible/base/nsAccUtils.cpp b/accessible/base/nsAccUtils.cpp
new file mode 100644
index 000000000..59db53c4b
--- /dev/null
+++ b/accessible/base/nsAccUtils.cpp
@@ -0,0 +1,447 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsAccUtils.h"
+
+#include "Accessible-inl.h"
+#include "ARIAMap.h"
+#include "nsAccessibilityService.h"
+#include "nsCoreUtils.h"
+#include "DocAccessible.h"
+#include "HyperTextAccessible.h"
+#include "nsIAccessibleTypes.h"
+#include "Role.h"
+#include "States.h"
+#include "TextLeafAccessible.h"
+
+#include "nsIDOMXULContainerElement.h"
+#include "nsIPersistentProperties2.h"
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+void
+nsAccUtils::GetAccAttr(nsIPersistentProperties *aAttributes,
+ nsIAtom *aAttrName, nsAString& aAttrValue)
+{
+ aAttrValue.Truncate();
+
+ aAttributes->GetStringProperty(nsAtomCString(aAttrName), aAttrValue);
+}
+
+void
+nsAccUtils::SetAccAttr(nsIPersistentProperties *aAttributes,
+ nsIAtom *aAttrName, const nsAString& aAttrValue)
+{
+ nsAutoString oldValue;
+ aAttributes->SetStringProperty(nsAtomCString(aAttrName), aAttrValue, oldValue);
+}
+
+void
+nsAccUtils::SetAccAttr(nsIPersistentProperties *aAttributes,
+ nsIAtom* aAttrName, nsIAtom* aAttrValue)
+{
+ nsAutoString oldValue;
+ aAttributes->SetStringProperty(nsAtomCString(aAttrName),
+ nsAtomString(aAttrValue), oldValue);
+}
+
+void
+nsAccUtils::SetAccGroupAttrs(nsIPersistentProperties *aAttributes,
+ int32_t aLevel, int32_t aSetSize,
+ int32_t aPosInSet)
+{
+ nsAutoString value;
+
+ if (aLevel) {
+ value.AppendInt(aLevel);
+ SetAccAttr(aAttributes, nsGkAtoms::level, value);
+ }
+
+ if (aSetSize && aPosInSet) {
+ value.Truncate();
+ value.AppendInt(aPosInSet);
+ SetAccAttr(aAttributes, nsGkAtoms::posinset, value);
+
+ value.Truncate();
+ value.AppendInt(aSetSize);
+ SetAccAttr(aAttributes, nsGkAtoms::setsize, value);
+ }
+}
+
+int32_t
+nsAccUtils::GetDefaultLevel(Accessible* aAccessible)
+{
+ roles::Role role = aAccessible->Role();
+
+ if (role == roles::OUTLINEITEM)
+ return 1;
+
+ if (role == roles::ROW) {
+ Accessible* parent = aAccessible->Parent();
+ // It is a row inside flatten treegrid. Group level is always 1 until it
+ // is overriden by aria-level attribute.
+ if (parent && parent->Role() == roles::TREE_TABLE)
+ return 1;
+ }
+
+ return 0;
+}
+
+int32_t
+nsAccUtils::GetARIAOrDefaultLevel(Accessible* aAccessible)
+{
+ int32_t level = 0;
+ nsCoreUtils::GetUIntAttr(aAccessible->GetContent(),
+ nsGkAtoms::aria_level, &level);
+
+ if (level != 0)
+ return level;
+
+ return GetDefaultLevel(aAccessible);
+}
+
+int32_t
+nsAccUtils::GetLevelForXULContainerItem(nsIContent *aContent)
+{
+ nsCOMPtr<nsIDOMXULContainerItemElement> item(do_QueryInterface(aContent));
+ if (!item)
+ return 0;
+
+ nsCOMPtr<nsIDOMXULContainerElement> container;
+ item->GetParentContainer(getter_AddRefs(container));
+ if (!container)
+ return 0;
+
+ // Get level of the item.
+ int32_t level = -1;
+ while (container) {
+ level++;
+
+ nsCOMPtr<nsIDOMXULContainerElement> parentContainer;
+ container->GetParentContainer(getter_AddRefs(parentContainer));
+ parentContainer.swap(container);
+ }
+
+ return level;
+}
+
+void
+nsAccUtils::SetLiveContainerAttributes(nsIPersistentProperties *aAttributes,
+ nsIContent* aStartContent,
+ dom::Element* aTopEl)
+{
+ nsAutoString live, relevant, busy;
+ nsIContent* ancestor = aStartContent;
+ while (ancestor) {
+
+ // container-relevant attribute
+ if (relevant.IsEmpty() &&
+ HasDefinedARIAToken(ancestor, nsGkAtoms::aria_relevant) &&
+ ancestor->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_relevant, relevant))
+ SetAccAttr(aAttributes, nsGkAtoms::containerRelevant, relevant);
+
+ // container-live, and container-live-role attributes
+ if (live.IsEmpty()) {
+ const nsRoleMapEntry* role = nullptr;
+ if (ancestor->IsElement()) {
+ role = aria::GetRoleMap(ancestor->AsElement());
+ }
+ if (HasDefinedARIAToken(ancestor, nsGkAtoms::aria_live)) {
+ ancestor->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_live, live);
+ } else if (role) {
+ GetLiveAttrValue(role->liveAttRule, live);
+ }
+ if (!live.IsEmpty()) {
+ SetAccAttr(aAttributes, nsGkAtoms::containerLive, live);
+ if (role) {
+ SetAccAttr(aAttributes, nsGkAtoms::containerLiveRole,
+ role->ARIARoleString());
+ }
+ }
+ }
+
+ // container-atomic attribute
+ if (ancestor->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_atomic,
+ nsGkAtoms::_true, eCaseMatters)) {
+ SetAccAttr(aAttributes, nsGkAtoms::containerAtomic,
+ NS_LITERAL_STRING("true"));
+ }
+
+ // container-busy attribute
+ if (busy.IsEmpty() &&
+ HasDefinedARIAToken(ancestor, nsGkAtoms::aria_busy) &&
+ ancestor->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_busy, busy))
+ SetAccAttr(aAttributes, nsGkAtoms::containerBusy, busy);
+
+ if (ancestor == aTopEl)
+ break;
+
+ ancestor = ancestor->GetParent();
+ if (!ancestor)
+ ancestor = aTopEl; // Use <body>/<frameset>
+ }
+}
+
+bool
+nsAccUtils::HasDefinedARIAToken(nsIContent *aContent, nsIAtom *aAtom)
+{
+ NS_ASSERTION(aContent, "aContent is null in call to HasDefinedARIAToken!");
+
+ if (!aContent->HasAttr(kNameSpaceID_None, aAtom) ||
+ aContent->AttrValueIs(kNameSpaceID_None, aAtom,
+ nsGkAtoms::_empty, eCaseMatters) ||
+ aContent->AttrValueIs(kNameSpaceID_None, aAtom,
+ nsGkAtoms::_undefined, eCaseMatters)) {
+ return false;
+ }
+ return true;
+}
+
+nsIAtom*
+nsAccUtils::GetARIAToken(dom::Element* aElement, nsIAtom* aAttr)
+{
+ if (!HasDefinedARIAToken(aElement, aAttr))
+ return nsGkAtoms::_empty;
+
+ static nsIContent::AttrValuesArray tokens[] =
+ { &nsGkAtoms::_false, &nsGkAtoms::_true,
+ &nsGkAtoms::mixed, nullptr};
+
+ int32_t idx = aElement->FindAttrValueIn(kNameSpaceID_None,
+ aAttr, tokens, eCaseMatters);
+ if (idx >= 0)
+ return *(tokens[idx]);
+
+ return nullptr;
+}
+
+Accessible*
+nsAccUtils::GetSelectableContainer(Accessible* aAccessible, uint64_t aState)
+{
+ if (!aAccessible)
+ return nullptr;
+
+ if (!(aState & states::SELECTABLE))
+ return nullptr;
+
+ Accessible* parent = aAccessible;
+ while ((parent = parent->Parent()) && !parent->IsSelect()) {
+ if (parent->Role() == roles::PANE)
+ return nullptr;
+ }
+ return parent;
+}
+
+bool
+nsAccUtils::IsARIASelected(Accessible* aAccessible)
+{
+ return aAccessible->GetContent()->
+ AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_selected,
+ nsGkAtoms::_true, eCaseMatters);
+}
+
+Accessible*
+nsAccUtils::TableFor(Accessible* aRow)
+{
+ if (aRow) {
+ Accessible* table = aRow->Parent();
+ if (table) {
+ roles::Role tableRole = table->Role();
+ if (tableRole == roles::GROUPING) { // if there's a rowgroup.
+ table = table->Parent();
+ if (table)
+ tableRole = table->Role();
+ }
+
+ return (tableRole == roles::TABLE || tableRole == roles::TREE_TABLE ||
+ tableRole == roles::MATHML_TABLE) ? table : nullptr;
+ }
+ }
+
+ return nullptr;
+}
+
+HyperTextAccessible*
+nsAccUtils::GetTextContainer(nsINode* aNode)
+{
+ // Get text accessible containing the result node.
+ DocAccessible* doc =
+ GetAccService()->GetDocAccessible(aNode->OwnerDoc());
+ Accessible* accessible =
+ doc ? doc->GetAccessibleOrContainer(aNode) : nullptr;
+ if (!accessible)
+ return nullptr;
+
+ do {
+ HyperTextAccessible* textAcc = accessible->AsHyperText();
+ if (textAcc)
+ return textAcc;
+
+ accessible = accessible->Parent();
+ } while (accessible);
+
+ return nullptr;
+}
+
+nsIntPoint
+nsAccUtils::ConvertToScreenCoords(int32_t aX, int32_t aY,
+ uint32_t aCoordinateType,
+ Accessible* aAccessible)
+{
+ nsIntPoint coords(aX, aY);
+
+ switch (aCoordinateType) {
+ case nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE:
+ break;
+
+ case nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE:
+ {
+ coords += nsCoreUtils::GetScreenCoordsForWindow(aAccessible->GetNode());
+ break;
+ }
+
+ case nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE:
+ {
+ coords += GetScreenCoordsForParent(aAccessible);
+ break;
+ }
+
+ default:
+ NS_NOTREACHED("invalid coord type!");
+ }
+
+ return coords;
+}
+
+void
+nsAccUtils::ConvertScreenCoordsTo(int32_t *aX, int32_t *aY,
+ uint32_t aCoordinateType,
+ Accessible* aAccessible)
+{
+ switch (aCoordinateType) {
+ case nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE:
+ break;
+
+ case nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE:
+ {
+ nsIntPoint coords = nsCoreUtils::GetScreenCoordsForWindow(aAccessible->GetNode());
+ *aX -= coords.x;
+ *aY -= coords.y;
+ break;
+ }
+
+ case nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE:
+ {
+ nsIntPoint coords = GetScreenCoordsForParent(aAccessible);
+ *aX -= coords.x;
+ *aY -= coords.y;
+ break;
+ }
+
+ default:
+ NS_NOTREACHED("invalid coord type!");
+ }
+}
+
+nsIntPoint
+nsAccUtils::GetScreenCoordsForParent(Accessible* aAccessible)
+{
+ Accessible* parent = aAccessible->Parent();
+ if (!parent)
+ return nsIntPoint(0, 0);
+
+ nsIFrame *parentFrame = parent->GetFrame();
+ if (!parentFrame)
+ return nsIntPoint(0, 0);
+
+ nsRect rect = parentFrame->GetScreenRectInAppUnits();
+ return nsPoint(rect.x, rect.y).
+ ToNearestPixels(parentFrame->PresContext()->AppUnitsPerDevPixel());
+}
+
+bool
+nsAccUtils::GetLiveAttrValue(uint32_t aRule, nsAString& aValue)
+{
+ switch (aRule) {
+ case eOffLiveAttr:
+ aValue = NS_LITERAL_STRING("off");
+ return true;
+ case ePoliteLiveAttr:
+ aValue = NS_LITERAL_STRING("polite");
+ return true;
+ }
+
+ return false;
+}
+
+#ifdef DEBUG
+
+bool
+nsAccUtils::IsTextInterfaceSupportCorrect(Accessible* aAccessible)
+{
+ // Don't test for accessible docs, it makes us create accessibles too
+ // early and fire mutation events before we need to
+ if (aAccessible->IsDoc())
+ return true;
+
+ bool foundText = false;
+ uint32_t childCount = aAccessible->ChildCount();
+ for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
+ Accessible* child = aAccessible->GetChildAt(childIdx);
+ if (child->IsText()) {
+ foundText = true;
+ break;
+ }
+ }
+
+ return !foundText || aAccessible->IsHyperText();
+}
+#endif
+
+uint32_t
+nsAccUtils::TextLength(Accessible* aAccessible)
+{
+ if (!aAccessible->IsText()) {
+ return 1;
+ }
+
+ TextLeafAccessible* textLeaf = aAccessible->AsTextLeaf();
+ if (textLeaf)
+ return textLeaf->Text().Length();
+
+ // For list bullets (or anything other accessible which would compute its own
+ // text. They don't have their own frame.
+ // XXX In the future, list bullets may have frame and anon content, so
+ // we should be able to remove this at that point
+ nsAutoString text;
+ aAccessible->AppendTextTo(text); // Get all the text
+ return text.Length();
+}
+
+bool
+nsAccUtils::MustPrune(Accessible* aAccessible)
+{
+ roles::Role role = aAccessible->Role();
+
+ // Don't prune the tree for certain roles if the tree is more complex than
+ // a single text leaf.
+ return
+ (role == roles::MENUITEM ||
+ role == roles::COMBOBOX_OPTION ||
+ role == roles::OPTION ||
+ role == roles::ENTRY ||
+ role == roles::FLAT_EQUATION ||
+ role == roles::PASSWORD_TEXT ||
+ role == roles::PUSHBUTTON ||
+ role == roles::TOGGLE_BUTTON ||
+ role == roles::GRAPHIC ||
+ role == roles::SLIDER ||
+ role == roles::PROGRESSBAR ||
+ role == roles::SEPARATOR) &&
+ aAccessible->ContentChildCount() == 1 &&
+ aAccessible->ContentChildAt(0)->IsTextLeaf();
+}
diff --git a/accessible/base/nsAccUtils.h b/accessible/base/nsAccUtils.h
new file mode 100644
index 000000000..9a9bc2590
--- /dev/null
+++ b/accessible/base/nsAccUtils.h
@@ -0,0 +1,243 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 nsAccUtils_h_
+#define nsAccUtils_h_
+
+#include "mozilla/a11y/Accessible.h"
+
+#include "nsAccessibilityService.h"
+#include "nsCoreUtils.h"
+
+#include "nsIDocShell.h"
+#include "nsPoint.h"
+
+namespace mozilla {
+
+namespace dom {
+class Element;
+}
+
+namespace a11y {
+
+class HyperTextAccessible;
+class DocAccessible;
+
+class nsAccUtils
+{
+public:
+ /**
+ * Returns value of attribute from the given attributes container.
+ *
+ * @param aAttributes - attributes container
+ * @param aAttrName - the name of requested attribute
+ * @param aAttrValue - value of attribute
+ */
+ static void GetAccAttr(nsIPersistentProperties *aAttributes,
+ nsIAtom *aAttrName,
+ nsAString& aAttrValue);
+
+ /**
+ * Set value of attribute for the given attributes container.
+ *
+ * @param aAttributes - attributes container
+ * @param aAttrName - the name of requested attribute
+ * @param aAttrValue - new value of attribute
+ */
+ static void SetAccAttr(nsIPersistentProperties *aAttributes,
+ nsIAtom *aAttrName,
+ const nsAString& aAttrValue);
+
+ static void SetAccAttr(nsIPersistentProperties *aAttributes,
+ nsIAtom* aAttrName,
+ nsIAtom* aAttrValue);
+
+ /**
+ * Set group attributes ('level', 'setsize', 'posinset').
+ */
+ static void SetAccGroupAttrs(nsIPersistentProperties *aAttributes,
+ int32_t aLevel, int32_t aSetSize,
+ int32_t aPosInSet);
+
+ /**
+ * Get default value of the level for the given accessible.
+ */
+ static int32_t GetDefaultLevel(Accessible* aAcc);
+
+ /**
+ * Return ARIA level value or the default one if ARIA is missed for the
+ * given accessible.
+ */
+ static int32_t GetARIAOrDefaultLevel(Accessible* aAccessible);
+
+ /**
+ * Compute group level for nsIDOMXULContainerItemElement node.
+ */
+ static int32_t GetLevelForXULContainerItem(nsIContent *aContent);
+
+ /**
+ * Set container-foo live region attributes for the given node.
+ *
+ * @param aAttributes where to store the attributes
+ * @param aStartContent node to start from
+ * @param aTopContent node to end at
+ */
+ static void SetLiveContainerAttributes(nsIPersistentProperties *aAttributes,
+ nsIContent* aStartContent,
+ mozilla::dom::Element* aTopEl);
+
+ /**
+ * Any ARIA property of type boolean or NMTOKEN is undefined if the ARIA
+ * property is not present, or is "" or "undefined". Do not call
+ * this method for properties of type string, decimal, IDREF or IDREFS.
+ *
+ * Return true if the ARIA property is defined, otherwise false
+ */
+ static bool HasDefinedARIAToken(nsIContent *aContent, nsIAtom *aAtom);
+
+ /**
+ * Return atomic value of ARIA attribute of boolean or NMTOKEN type.
+ */
+ static nsIAtom* GetARIAToken(mozilla::dom::Element* aElement, nsIAtom* aAttr);
+
+ /**
+ * Return document accessible for the given DOM node.
+ */
+ static DocAccessible* GetDocAccessibleFor(nsINode* aNode)
+ {
+ nsIPresShell *presShell = nsCoreUtils::GetPresShellFor(aNode);
+ return GetAccService()->GetDocAccessible(presShell);
+ }
+
+ /**
+ * Return document accessible for the given docshell.
+ */
+ static DocAccessible* GetDocAccessibleFor(nsIDocShellTreeItem* aContainer)
+ {
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aContainer));
+ return GetAccService()->GetDocAccessible(docShell->GetPresShell());
+ }
+
+ /**
+ * Return single or multi selectable container for the given item.
+ *
+ * @param aAccessible [in] the item accessible
+ * @param aState [in] the state of the item accessible
+ */
+ static Accessible* GetSelectableContainer(Accessible* aAccessible,
+ uint64_t aState);
+
+ /**
+ * Return a text container accessible for the given node.
+ */
+ static HyperTextAccessible* GetTextContainer(nsINode* aNode);
+
+ static Accessible* TableFor(Accessible* aRow);
+
+ /**
+ * Return true if the DOM node of given accessible has aria-selected="true"
+ * attribute.
+ */
+ static bool IsARIASelected(Accessible* aAccessible);
+
+ /**
+ * Converts the given coordinates to coordinates relative screen.
+ *
+ * @param aX [in] the given x coord
+ * @param aY [in] the given y coord
+ * @param aCoordinateType [in] specifies coordinates origin (refer to
+ * nsIAccessibleCoordinateType)
+ * @param aAccessible [in] the accessible if coordinates are given
+ * relative it.
+ * @return converted coordinates
+ */
+ static nsIntPoint ConvertToScreenCoords(int32_t aX, int32_t aY,
+ uint32_t aCoordinateType,
+ Accessible* aAccessible);
+
+ /**
+ * Converts the given coordinates relative screen to another coordinate
+ * system.
+ *
+ * @param aX [in, out] the given x coord
+ * @param aY [in, out] the given y coord
+ * @param aCoordinateType [in] specifies coordinates origin (refer to
+ * nsIAccessibleCoordinateType)
+ * @param aAccessible [in] the accessible if coordinates are given
+ * relative it
+ */
+ static void ConvertScreenCoordsTo(int32_t* aX, int32_t* aY,
+ uint32_t aCoordinateType,
+ Accessible* aAccessible);
+
+ /**
+ * Returns coordinates relative screen for the parent of the given accessible.
+ *
+ * @param [in] aAccessible the accessible
+ */
+ static nsIntPoint GetScreenCoordsForParent(Accessible* aAccessible);
+
+ /**
+ * Get the 'live' or 'container-live' object attribute value from the given
+ * ELiveAttrRule constant.
+ *
+ * @param aRule [in] rule constant (see ELiveAttrRule in nsAccMap.h)
+ * @param aValue [out] object attribute value
+ *
+ * @return true if object attribute should be exposed
+ */
+ static bool GetLiveAttrValue(uint32_t aRule, nsAString& aValue);
+
+#ifdef DEBUG
+ /**
+ * Detect whether the given accessible object implements nsIAccessibleText,
+ * when it is text or has text child node.
+ */
+ static bool IsTextInterfaceSupportCorrect(Accessible* aAccessible);
+#endif
+
+ /**
+ * Return text length of the given accessible, return 0 on failure.
+ */
+ static uint32_t TextLength(Accessible* aAccessible);
+
+ /**
+ * Transform nsIAccessibleStates constants to internal state constant.
+ */
+ static inline uint64_t To64State(uint32_t aState1, uint32_t aState2)
+ {
+ return static_cast<uint64_t>(aState1) +
+ (static_cast<uint64_t>(aState2) << 31);
+ }
+
+ /**
+ * Transform internal state constant to nsIAccessibleStates constants.
+ */
+ static inline void To32States(uint64_t aState64,
+ uint32_t* aState1, uint32_t* aState2)
+ {
+ *aState1 = aState64 & 0x7fffffff;
+ if (aState2)
+ *aState2 = static_cast<uint32_t>(aState64 >> 31);
+ }
+
+ static uint32_t To32States(uint64_t aState, bool* aIsExtra)
+ {
+ uint32_t extraState = aState >> 31;
+ *aIsExtra = !!extraState;
+ return aState | extraState;
+ }
+
+ /**
+ * Return true if the given accessible can't have children. Used when exposing
+ * to platform accessibility APIs, should the children be pruned off?
+ */
+ static bool MustPrune(Accessible* aAccessible);
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/base/nsAccessibilityService.cpp b/accessible/base/nsAccessibilityService.cpp
new file mode 100644
index 000000000..2590969a0
--- /dev/null
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -0,0 +1,1885 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsAccessibilityService.h"
+
+// NOTE: alphabetically ordered
+#include "ApplicationAccessibleWrap.h"
+#include "ARIAGridAccessibleWrap.h"
+#include "ARIAMap.h"
+#include "DocAccessible-inl.h"
+#include "FocusManager.h"
+#include "HTMLCanvasAccessible.h"
+#include "HTMLElementAccessibles.h"
+#include "HTMLImageMapAccessible.h"
+#include "HTMLLinkAccessible.h"
+#include "HTMLListAccessible.h"
+#include "HTMLSelectAccessible.h"
+#include "HTMLTableAccessibleWrap.h"
+#include "HyperTextAccessibleWrap.h"
+#include "RootAccessible.h"
+#include "nsAccUtils.h"
+#include "nsArrayUtils.h"
+#include "nsAttrName.h"
+#include "nsEventShell.h"
+#include "nsIURI.h"
+#include "OuterDocAccessible.h"
+#include "Platform.h"
+#include "Role.h"
+#ifdef MOZ_ACCESSIBILITY_ATK
+#include "RootAccessibleWrap.h"
+#endif
+#include "States.h"
+#include "Statistics.h"
+#include "TextLeafAccessibleWrap.h"
+#include "TreeWalker.h"
+#include "xpcAccessibleApplication.h"
+#include "xpcAccessibleDocument.h"
+
+#ifdef MOZ_ACCESSIBILITY_ATK
+#include "AtkSocketAccessible.h"
+#endif
+
+#ifdef XP_WIN
+#include "mozilla/a11y/Compatibility.h"
+#include "mozilla/dom/ContentChild.h"
+#include "HTMLWin32ObjectAccessible.h"
+#include "mozilla/StaticPtr.h"
+#endif
+
+#ifdef A11Y_LOG
+#include "Logging.h"
+#endif
+
+#ifdef MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
+#endif
+
+#include "nsImageFrame.h"
+#include "nsIObserverService.h"
+#include "nsLayoutUtils.h"
+#include "nsPluginFrame.h"
+#include "nsSVGPathGeometryFrame.h"
+#include "nsTreeBodyFrame.h"
+#include "nsTreeColumns.h"
+#include "nsTreeUtils.h"
+#include "nsXBLPrototypeBinding.h"
+#include "nsXBLBinding.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/dom/DOMStringList.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "nsDeckFrame.h"
+
+#ifdef MOZ_XUL
+#include "XULAlertAccessible.h"
+#include "XULColorPickerAccessible.h"
+#include "XULComboboxAccessible.h"
+#include "XULElementAccessibles.h"
+#include "XULFormControlAccessible.h"
+#include "XULListboxAccessibleWrap.h"
+#include "XULMenuAccessibleWrap.h"
+#include "XULSliderAccessible.h"
+#include "XULTabAccessible.h"
+#include "XULTreeGridAccessibleWrap.h"
+#endif
+
+#if defined(XP_WIN) || defined(MOZ_ACCESSIBILITY_ATK)
+#include "nsNPAPIPluginInstance.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+using namespace mozilla::dom;
+
+////////////////////////////////////////////////////////////////////////////////
+// Statics
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Return true if the element must be accessible.
+ */
+static bool
+MustBeAccessible(nsIContent* aContent, DocAccessible* aDocument)
+{
+ if (aContent->GetPrimaryFrame()->IsFocusable())
+ return true;
+
+ uint32_t attrCount = aContent->GetAttrCount();
+ for (uint32_t attrIdx = 0; attrIdx < attrCount; attrIdx++) {
+ const nsAttrName* attr = aContent->GetAttrNameAt(attrIdx);
+ if (attr->NamespaceEquals(kNameSpaceID_None)) {
+ nsIAtom* attrAtom = attr->Atom();
+ nsDependentAtomString attrStr(attrAtom);
+ if (!StringBeginsWith(attrStr, NS_LITERAL_STRING("aria-")))
+ continue; // not ARIA
+
+ // A global state or a property and in case of token defined.
+ uint8_t attrFlags = aria::AttrCharacteristicsFor(attrAtom);
+ if ((attrFlags & ATTR_GLOBAL) && (!(attrFlags & ATTR_VALTOKEN) ||
+ nsAccUtils::HasDefinedARIAToken(aContent, attrAtom))) {
+ return true;
+ }
+ }
+ }
+
+ // If the given ID is referred by relation attribute then create an accessible
+ // for it.
+ nsAutoString id;
+ if (nsCoreUtils::GetID(aContent, id) && !id.IsEmpty())
+ return aDocument->IsDependentID(id);
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible constructors
+
+static Accessible*
+New_HTMLLink(nsIContent* aContent, Accessible* aContext)
+{
+ // Only some roles truly enjoy life as HTMLLinkAccessibles, for details
+ // see closed bug 494807.
+ const nsRoleMapEntry* roleMapEntry = aria::GetRoleMap(aContent->AsElement());
+ if (roleMapEntry && roleMapEntry->role != roles::NOTHING &&
+ roleMapEntry->role != roles::LINK) {
+ return new HyperTextAccessibleWrap(aContent, aContext->Document());
+ }
+
+ return new HTMLLinkAccessible(aContent, aContext->Document());
+}
+
+static Accessible* New_HyperText(nsIContent* aContent, Accessible* aContext)
+ { return new HyperTextAccessibleWrap(aContent, aContext->Document()); }
+
+static Accessible* New_HTMLFigcaption(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLFigcaptionAccessible(aContent, aContext->Document()); }
+
+static Accessible* New_HTMLFigure(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLFigureAccessible(aContent, aContext->Document()); }
+
+static Accessible* New_HTMLLegend(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLLegendAccessible(aContent, aContext->Document()); }
+
+static Accessible* New_HTMLOption(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLSelectOptionAccessible(aContent, aContext->Document()); }
+
+static Accessible* New_HTMLOptgroup(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLSelectOptGroupAccessible(aContent, aContext->Document()); }
+
+static Accessible* New_HTMLList(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLListAccessible(aContent, aContext->Document()); }
+
+static Accessible*
+New_HTMLListitem(nsIContent* aContent, Accessible* aContext)
+{
+ // If list item is a child of accessible list then create an accessible for
+ // it unconditionally by tag name. nsBlockFrame creates the list item
+ // accessible for other elements styled as list items.
+ if (aContext->IsList() && aContext->GetContent() == aContent->GetParent())
+ return new HTMLLIAccessible(aContent, aContext->Document());
+
+ return nullptr;
+}
+
+static Accessible*
+New_HTMLDefinition(nsIContent* aContent, Accessible* aContext)
+{
+ if (aContext->IsList())
+ return new HyperTextAccessibleWrap(aContent, aContext->Document());
+ return nullptr;
+}
+
+static Accessible* New_HTMLLabel(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLLabelAccessible(aContent, aContext->Document()); }
+
+static Accessible* New_HTMLOutput(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLOutputAccessible(aContent, aContext->Document()); }
+
+static Accessible* New_HTMLProgress(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLProgressMeterAccessible(aContent, aContext->Document()); }
+
+static Accessible* New_HTMLSummary(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLSummaryAccessible(aContent, aContext->Document()); }
+
+static Accessible*
+New_HTMLTableAccessible(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLTableAccessible(aContent, aContext->Document()); }
+
+static Accessible*
+New_HTMLTableRowAccessible(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLTableRowAccessible(aContent, aContext->Document()); }
+
+static Accessible*
+New_HTMLTableCellAccessible(nsIContent* aContent, Accessible* aContext)
+ { return new HTMLTableCellAccessible(aContent, aContext->Document()); }
+
+static Accessible*
+New_HTMLTableHeaderCell(nsIContent* aContent, Accessible* aContext)
+{
+ if (aContext->IsTableRow() && aContext->GetContent() == aContent->GetParent())
+ return new HTMLTableHeaderCellAccessibleWrap(aContent, aContext->Document());
+ return nullptr;
+}
+
+static Accessible*
+New_HTMLTableHeaderCellIfScope(nsIContent* aContent, Accessible* aContext)
+{
+ if (aContext->IsTableRow() && aContext->GetContent() == aContent->GetParent() &&
+ aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::scope))
+ return new HTMLTableHeaderCellAccessibleWrap(aContent, aContext->Document());
+ return nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Markup maps array.
+
+#define Attr(name, value) \
+ { &nsGkAtoms::name, &nsGkAtoms::value }
+
+#define AttrFromDOM(name, DOMAttrName) \
+ { &nsGkAtoms::name, nullptr, &nsGkAtoms::DOMAttrName }
+
+#define AttrFromDOMIf(name, DOMAttrName, DOMAttrValue) \
+ { &nsGkAtoms::name, nullptr, &nsGkAtoms::DOMAttrName, &nsGkAtoms::DOMAttrValue }
+
+#define MARKUPMAP(atom, new_func, r, ... ) \
+ { &nsGkAtoms::atom, new_func, static_cast<a11y::role>(r), { __VA_ARGS__ } },
+
+static const MarkupMapInfo sMarkupMapList[] = {
+ #include "MarkupMap.h"
+};
+
+#undef Attr
+#undef AttrFromDOM
+#undef AttrFromDOMIf
+#undef MARKUPMAP
+
+////////////////////////////////////////////////////////////////////////////////
+// nsAccessibilityService
+////////////////////////////////////////////////////////////////////////////////
+
+nsAccessibilityService *nsAccessibilityService::gAccessibilityService = nullptr;
+ApplicationAccessible* nsAccessibilityService::gApplicationAccessible = nullptr;
+xpcAccessibleApplication* nsAccessibilityService::gXPCApplicationAccessible = nullptr;
+uint32_t nsAccessibilityService::gConsumers = 0;
+
+nsAccessibilityService::nsAccessibilityService() :
+ DocManager(), FocusManager(), mMarkupMaps(ArrayLength(sMarkupMapList))
+{
+}
+
+nsAccessibilityService::~nsAccessibilityService()
+{
+ NS_ASSERTION(IsShutdown(), "Accessibility wasn't shutdown!");
+ gAccessibilityService = nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIListenerChangeListener
+
+NS_IMETHODIMP
+nsAccessibilityService::ListenersChanged(nsIArray* aEventChanges)
+{
+ uint32_t targetCount;
+ nsresult rv = aEventChanges->GetLength(&targetCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0 ; i < targetCount ; i++) {
+ nsCOMPtr<nsIEventListenerChange> change = do_QueryElementAt(aEventChanges, i);
+
+ nsCOMPtr<nsIDOMEventTarget> target;
+ change->GetTarget(getter_AddRefs(target));
+ nsCOMPtr<nsIContent> node(do_QueryInterface(target));
+ if (!node || !node->IsHTMLElement()) {
+ continue;
+ }
+ nsCOMPtr<nsIArray> listenerNames;
+ change->GetChangedListenerNames(getter_AddRefs(listenerNames));
+
+ uint32_t changeCount;
+ rv = listenerNames->GetLength(&changeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0 ; i < changeCount ; i++) {
+ nsCOMPtr<nsIAtom> listenerName = do_QueryElementAt(listenerNames, i);
+
+ // We are only interested in event listener changes which may
+ // make an element accessible or inaccessible.
+ if (listenerName != nsGkAtoms::onclick &&
+ listenerName != nsGkAtoms::onmousedown &&
+ listenerName != nsGkAtoms::onmouseup) {
+ continue;
+ }
+
+ nsIDocument* ownerDoc = node->OwnerDoc();
+ DocAccessible* document = GetExistingDocAccessible(ownerDoc);
+
+ // Create an accessible for a inaccessible element having click event
+ // handler.
+ if (document && !document->HasAccessible(node) &&
+ nsCoreUtils::HasClickListener(node)) {
+ nsIContent* parentEl = node->GetFlattenedTreeParent();
+ if (parentEl) {
+ document->ContentInserted(parentEl, node, node->GetNextSibling());
+ }
+ break;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAccessibilityService,
+ DocManager,
+ nsIObserver,
+ nsIListenerChangeListener,
+ nsISelectionListener) // from SelectionManager
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIObserver
+
+NS_IMETHODIMP
+nsAccessibilityService::Observe(nsISupports *aSubject, const char *aTopic,
+ const char16_t *aData)
+{
+ if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID))
+ Shutdown();
+
+ return NS_OK;
+}
+
+void
+nsAccessibilityService::NotifyOfAnchorJumpTo(nsIContent* aTargetNode)
+{
+ nsIDocument* documentNode = aTargetNode->GetUncomposedDoc();
+ if (documentNode) {
+ DocAccessible* document = GetDocAccessible(documentNode);
+ if (document)
+ document->SetAnchorJump(aTargetNode);
+ }
+}
+
+void
+nsAccessibilityService::FireAccessibleEvent(uint32_t aEvent,
+ Accessible* aTarget)
+{
+ nsEventShell::FireEvent(aEvent, aTarget);
+}
+
+Accessible*
+nsAccessibilityService::GetRootDocumentAccessible(nsIPresShell* aPresShell,
+ bool aCanCreate)
+{
+ nsIPresShell* ps = aPresShell;
+ nsIDocument* documentNode = aPresShell->GetDocument();
+ if (documentNode) {
+ nsCOMPtr<nsIDocShellTreeItem> treeItem(documentNode->GetDocShell());
+ if (treeItem) {
+ nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
+ treeItem->GetRootTreeItem(getter_AddRefs(rootTreeItem));
+ if (treeItem != rootTreeItem) {
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(rootTreeItem));
+ ps = docShell->GetPresShell();
+ }
+
+ return aCanCreate ? GetDocAccessible(ps) : ps->GetDocAccessible();
+ }
+ }
+ return nullptr;
+}
+
+#ifdef XP_WIN
+static StaticAutoPtr<nsTArray<nsCOMPtr<nsIContent> > > sPendingPlugins;
+static StaticAutoPtr<nsTArray<nsCOMPtr<nsITimer> > > sPluginTimers;
+
+class PluginTimerCallBack final : public nsITimerCallback
+{
+ ~PluginTimerCallBack() {}
+
+public:
+ PluginTimerCallBack(nsIContent* aContent) : mContent(aContent) {}
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Notify(nsITimer* aTimer) final
+ {
+ if (!mContent->IsInUncomposedDoc())
+ return NS_OK;
+
+ nsIPresShell* ps = mContent->OwnerDoc()->GetShell();
+ if (ps) {
+ DocAccessible* doc = ps->GetDocAccessible();
+ if (doc) {
+ // Make sure that if we created an accessible for the plugin that wasn't
+ // a plugin accessible we remove it before creating the right accessible.
+ doc->RecreateAccessible(mContent);
+ sPluginTimers->RemoveElement(aTimer);
+ return NS_OK;
+ }
+ }
+
+ // We couldn't get a doc accessible so presumably the document went away.
+ // In this case don't leak our ref to the content or timer.
+ sPendingPlugins->RemoveElement(mContent);
+ sPluginTimers->RemoveElement(aTimer);
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIContent> mContent;
+};
+
+NS_IMPL_ISUPPORTS(PluginTimerCallBack, nsITimerCallback)
+#endif
+
+already_AddRefed<Accessible>
+nsAccessibilityService::CreatePluginAccessible(nsPluginFrame* aFrame,
+ nsIContent* aContent,
+ Accessible* aContext)
+{
+ // nsPluginFrame means a plugin, so we need to use the accessibility support
+ // of the plugin.
+ if (aFrame->GetRect().IsEmpty())
+ return nullptr;
+
+#if defined(XP_WIN) || defined(MOZ_ACCESSIBILITY_ATK)
+ RefPtr<nsNPAPIPluginInstance> pluginInstance;
+ if (NS_SUCCEEDED(aFrame->GetPluginInstance(getter_AddRefs(pluginInstance))) &&
+ pluginInstance) {
+#ifdef XP_WIN
+ if (!sPendingPlugins->Contains(aContent) &&
+ (Preferences::GetBool("accessibility.delay_plugins") ||
+ Compatibility::IsJAWS() || Compatibility::IsWE())) {
+ nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ RefPtr<PluginTimerCallBack> cb = new PluginTimerCallBack(aContent);
+ timer->InitWithCallback(cb, Preferences::GetUint("accessibility.delay_plugin_time"),
+ nsITimer::TYPE_ONE_SHOT);
+ sPluginTimers->AppendElement(timer);
+ sPendingPlugins->AppendElement(aContent);
+ return nullptr;
+ }
+
+ // We need to remove aContent from the pending plugins here to avoid
+ // reentrancy. When the timer fires it calls
+ // DocAccessible::ContentInserted() which does the work async.
+ sPendingPlugins->RemoveElement(aContent);
+
+ // Note: pluginPort will be null if windowless.
+ HWND pluginPort = nullptr;
+ aFrame->GetPluginPort(&pluginPort);
+
+ RefPtr<Accessible> accessible =
+ new HTMLWin32ObjectOwnerAccessible(aContent, aContext->Document(),
+ pluginPort);
+ return accessible.forget();
+
+#elif MOZ_ACCESSIBILITY_ATK
+ if (!AtkSocketAccessible::gCanEmbed)
+ return nullptr;
+
+ // Note this calls into the plugin, so crazy things may happen and aFrame
+ // may go away.
+ nsCString plugId;
+ nsresult rv = pluginInstance->GetValueFromPlugin(
+ NPPVpluginNativeAccessibleAtkPlugId, &plugId);
+ if (NS_SUCCEEDED(rv) && !plugId.IsEmpty()) {
+ RefPtr<AtkSocketAccessible> socketAccessible =
+ new AtkSocketAccessible(aContent, aContext->Document(), plugId);
+
+ return socketAccessible.forget();
+ }
+#endif
+ }
+#endif
+
+ return nullptr;
+}
+
+void
+nsAccessibilityService::DeckPanelSwitched(nsIPresShell* aPresShell,
+ nsIContent* aDeckNode,
+ nsIFrame* aPrevBoxFrame,
+ nsIFrame* aCurrentBoxFrame)
+{
+ // Ignore tabpanels elements (a deck having an accessible) since their
+ // children are accessible not depending on selected tab.
+ DocAccessible* document = GetDocAccessible(aPresShell);
+ if (!document || document->HasAccessible(aDeckNode))
+ return;
+
+ if (aPrevBoxFrame) {
+ nsIContent* panelNode = aPrevBoxFrame->GetContent();
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eTree)) {
+ logging::MsgBegin("TREE", "deck panel unselected");
+ logging::Node("container", panelNode);
+ logging::Node("content", aDeckNode);
+ logging::MsgEnd();
+ }
+#endif
+
+ document->ContentRemoved(aDeckNode, panelNode);
+ }
+
+ if (aCurrentBoxFrame) {
+ nsIContent* panelNode = aCurrentBoxFrame->GetContent();
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eTree)) {
+ logging::MsgBegin("TREE", "deck panel selected");
+ logging::Node("container", panelNode);
+ logging::Node("content", aDeckNode);
+ logging::MsgEnd();
+ }
+#endif
+
+ document->ContentInserted(aDeckNode, panelNode, panelNode->GetNextSibling());
+ }
+}
+
+void
+nsAccessibilityService::ContentRangeInserted(nsIPresShell* aPresShell,
+ nsIContent* aContainer,
+ nsIContent* aStartChild,
+ nsIContent* aEndChild)
+{
+ DocAccessible* document = GetDocAccessible(aPresShell);
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eTree)) {
+ logging::MsgBegin("TREE", "content inserted; doc: %p", document);
+ logging::Node("container", aContainer);
+ for (nsIContent* child = aStartChild; child != aEndChild;
+ child = child->GetNextSibling()) {
+ logging::Node("content", child);
+ }
+ logging::MsgEnd();
+ logging::Stack();
+ }
+#endif
+
+ if (document) {
+ document->ContentInserted(aContainer, aStartChild, aEndChild);
+ }
+}
+
+void
+nsAccessibilityService::ContentRemoved(nsIPresShell* aPresShell,
+ nsIContent* aChildNode)
+{
+ DocAccessible* document = GetDocAccessible(aPresShell);
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eTree)) {
+ logging::MsgBegin("TREE", "content removed; doc: %p", document);
+ logging::Node("container node", aChildNode->GetFlattenedTreeParent());
+ logging::Node("content node", aChildNode);
+ logging::MsgEnd();
+ }
+#endif
+
+ if (document) {
+ // Flatten hierarchy may be broken at this point so we cannot get a true
+ // container by traversing up the DOM tree. Find a parent of first accessible
+ // from the subtree of the given DOM node, that'll be a container. If no
+ // accessibles in subtree then we don't care about the change.
+ Accessible* child = document->GetAccessible(aChildNode);
+ if (!child) {
+ Accessible* container = document->GetContainerAccessible(aChildNode);
+ a11y::TreeWalker walker(container ? container : document, aChildNode,
+ a11y::TreeWalker::eWalkCache);
+ child = walker.Next();
+ }
+
+ if (child) {
+ document->ContentRemoved(child->Parent(), aChildNode);
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eTree))
+ logging::AccessibleNNode("real container", child->Parent());
+#endif
+ }
+ }
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eTree)) {
+ logging::MsgEnd();
+ logging::Stack();
+ }
+#endif
+}
+
+void
+nsAccessibilityService::UpdateText(nsIPresShell* aPresShell,
+ nsIContent* aContent)
+{
+ DocAccessible* document = GetDocAccessible(aPresShell);
+ if (document)
+ document->UpdateText(aContent);
+}
+
+void
+nsAccessibilityService::TreeViewChanged(nsIPresShell* aPresShell,
+ nsIContent* aContent,
+ nsITreeView* aView)
+{
+ DocAccessible* document = GetDocAccessible(aPresShell);
+ if (document) {
+ Accessible* accessible = document->GetAccessible(aContent);
+ if (accessible) {
+ XULTreeAccessible* treeAcc = accessible->AsXULTree();
+ if (treeAcc)
+ treeAcc->TreeViewChanged(aView);
+ }
+ }
+}
+
+void
+nsAccessibilityService::RangeValueChanged(nsIPresShell* aPresShell,
+ nsIContent* aContent)
+{
+ DocAccessible* document = GetDocAccessible(aPresShell);
+ if (document) {
+ Accessible* accessible = document->GetAccessible(aContent);
+ if (accessible) {
+ document->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE,
+ accessible);
+ }
+ }
+}
+
+void
+nsAccessibilityService::UpdateListBullet(nsIPresShell* aPresShell,
+ nsIContent* aHTMLListItemContent,
+ bool aHasBullet)
+{
+ DocAccessible* document = GetDocAccessible(aPresShell);
+ if (document) {
+ Accessible* accessible = document->GetAccessible(aHTMLListItemContent);
+ if (accessible) {
+ HTMLLIAccessible* listItem = accessible->AsHTMLListItem();
+ if (listItem)
+ listItem->UpdateBullet(aHasBullet);
+ }
+ }
+}
+
+void
+nsAccessibilityService::UpdateImageMap(nsImageFrame* aImageFrame)
+{
+ nsIPresShell* presShell = aImageFrame->PresContext()->PresShell();
+ DocAccessible* document = GetDocAccessible(presShell);
+ if (document) {
+ Accessible* accessible =
+ document->GetAccessible(aImageFrame->GetContent());
+ if (accessible) {
+ HTMLImageMapAccessible* imageMap = accessible->AsImageMap();
+ if (imageMap) {
+ imageMap->UpdateChildAreas();
+ return;
+ }
+
+ // If image map was initialized after we created an accessible (that'll
+ // be an image accessible) then recreate it.
+ RecreateAccessible(presShell, aImageFrame->GetContent());
+ }
+ }
+}
+
+void
+nsAccessibilityService::UpdateLabelValue(nsIPresShell* aPresShell,
+ nsIContent* aLabelElm,
+ const nsString& aNewValue)
+{
+ DocAccessible* document = GetDocAccessible(aPresShell);
+ if (document) {
+ Accessible* accessible = document->GetAccessible(aLabelElm);
+ if (accessible) {
+ XULLabelAccessible* xulLabel = accessible->AsXULLabel();
+ NS_ASSERTION(xulLabel,
+ "UpdateLabelValue was called for wrong accessible!");
+ if (xulLabel)
+ xulLabel->UpdateLabelValue(aNewValue);
+ }
+ }
+}
+
+void
+nsAccessibilityService::PresShellActivated(nsIPresShell* aPresShell)
+{
+ DocAccessible* document = aPresShell->GetDocAccessible();
+ if (document) {
+ RootAccessible* rootDocument = document->RootAccessible();
+ NS_ASSERTION(rootDocument, "Entirely broken tree: no root document!");
+ if (rootDocument)
+ rootDocument->DocumentActivated(document);
+ }
+}
+
+void
+nsAccessibilityService::RecreateAccessible(nsIPresShell* aPresShell,
+ nsIContent* aContent)
+{
+ DocAccessible* document = GetDocAccessible(aPresShell);
+ if (document)
+ document->RecreateAccessible(aContent);
+}
+
+void
+nsAccessibilityService::GetStringRole(uint32_t aRole, nsAString& aString)
+{
+#define ROLE(geckoRole, stringRole, atkRole, \
+ macRole, msaaRole, ia2Role, nameRule) \
+ case roles::geckoRole: \
+ CopyUTF8toUTF16(stringRole, aString); \
+ return;
+
+ switch (aRole) {
+#include "RoleMap.h"
+ default:
+ aString.AssignLiteral("unknown");
+ return;
+ }
+
+#undef ROLE
+}
+
+void
+nsAccessibilityService::GetStringStates(uint32_t aState, uint32_t aExtraState,
+ nsISupports **aStringStates)
+{
+ RefPtr<DOMStringList> stringStates = new DOMStringList();
+
+ uint64_t state = nsAccUtils::To64State(aState, aExtraState);
+
+ // states
+ if (state & states::UNAVAILABLE) {
+ stringStates->Add(NS_LITERAL_STRING("unavailable"));
+ }
+ if (state & states::SELECTED) {
+ stringStates->Add(NS_LITERAL_STRING("selected"));
+ }
+ if (state & states::FOCUSED) {
+ stringStates->Add(NS_LITERAL_STRING("focused"));
+ }
+ if (state & states::PRESSED) {
+ stringStates->Add(NS_LITERAL_STRING("pressed"));
+ }
+ if (state & states::CHECKED) {
+ stringStates->Add(NS_LITERAL_STRING("checked"));
+ }
+ if (state & states::MIXED) {
+ stringStates->Add(NS_LITERAL_STRING("mixed"));
+ }
+ if (state & states::READONLY) {
+ stringStates->Add(NS_LITERAL_STRING("readonly"));
+ }
+ if (state & states::HOTTRACKED) {
+ stringStates->Add(NS_LITERAL_STRING("hottracked"));
+ }
+ if (state & states::DEFAULT) {
+ stringStates->Add(NS_LITERAL_STRING("default"));
+ }
+ if (state & states::EXPANDED) {
+ stringStates->Add(NS_LITERAL_STRING("expanded"));
+ }
+ if (state & states::COLLAPSED) {
+ stringStates->Add(NS_LITERAL_STRING("collapsed"));
+ }
+ if (state & states::BUSY) {
+ stringStates->Add(NS_LITERAL_STRING("busy"));
+ }
+ if (state & states::FLOATING) {
+ stringStates->Add(NS_LITERAL_STRING("floating"));
+ }
+ if (state & states::ANIMATED) {
+ stringStates->Add(NS_LITERAL_STRING("animated"));
+ }
+ if (state & states::INVISIBLE) {
+ stringStates->Add(NS_LITERAL_STRING("invisible"));
+ }
+ if (state & states::OFFSCREEN) {
+ stringStates->Add(NS_LITERAL_STRING("offscreen"));
+ }
+ if (state & states::SIZEABLE) {
+ stringStates->Add(NS_LITERAL_STRING("sizeable"));
+ }
+ if (state & states::MOVEABLE) {
+ stringStates->Add(NS_LITERAL_STRING("moveable"));
+ }
+ if (state & states::SELFVOICING) {
+ stringStates->Add(NS_LITERAL_STRING("selfvoicing"));
+ }
+ if (state & states::FOCUSABLE) {
+ stringStates->Add(NS_LITERAL_STRING("focusable"));
+ }
+ if (state & states::SELECTABLE) {
+ stringStates->Add(NS_LITERAL_STRING("selectable"));
+ }
+ if (state & states::LINKED) {
+ stringStates->Add(NS_LITERAL_STRING("linked"));
+ }
+ if (state & states::TRAVERSED) {
+ stringStates->Add(NS_LITERAL_STRING("traversed"));
+ }
+ if (state & states::MULTISELECTABLE) {
+ stringStates->Add(NS_LITERAL_STRING("multiselectable"));
+ }
+ if (state & states::EXTSELECTABLE) {
+ stringStates->Add(NS_LITERAL_STRING("extselectable"));
+ }
+ if (state & states::PROTECTED) {
+ stringStates->Add(NS_LITERAL_STRING("protected"));
+ }
+ if (state & states::HASPOPUP) {
+ stringStates->Add(NS_LITERAL_STRING("haspopup"));
+ }
+ if (state & states::REQUIRED) {
+ stringStates->Add(NS_LITERAL_STRING("required"));
+ }
+ if (state & states::ALERT) {
+ stringStates->Add(NS_LITERAL_STRING("alert"));
+ }
+ if (state & states::INVALID) {
+ stringStates->Add(NS_LITERAL_STRING("invalid"));
+ }
+ if (state & states::CHECKABLE) {
+ stringStates->Add(NS_LITERAL_STRING("checkable"));
+ }
+
+ // extraStates
+ if (state & states::SUPPORTS_AUTOCOMPLETION) {
+ stringStates->Add(NS_LITERAL_STRING("autocompletion"));
+ }
+ if (state & states::DEFUNCT) {
+ stringStates->Add(NS_LITERAL_STRING("defunct"));
+ }
+ if (state & states::SELECTABLE_TEXT) {
+ stringStates->Add(NS_LITERAL_STRING("selectable text"));
+ }
+ if (state & states::EDITABLE) {
+ stringStates->Add(NS_LITERAL_STRING("editable"));
+ }
+ if (state & states::ACTIVE) {
+ stringStates->Add(NS_LITERAL_STRING("active"));
+ }
+ if (state & states::MODAL) {
+ stringStates->Add(NS_LITERAL_STRING("modal"));
+ }
+ if (state & states::MULTI_LINE) {
+ stringStates->Add(NS_LITERAL_STRING("multi line"));
+ }
+ if (state & states::HORIZONTAL) {
+ stringStates->Add(NS_LITERAL_STRING("horizontal"));
+ }
+ if (state & states::OPAQUE1) {
+ stringStates->Add(NS_LITERAL_STRING("opaque"));
+ }
+ if (state & states::SINGLE_LINE) {
+ stringStates->Add(NS_LITERAL_STRING("single line"));
+ }
+ if (state & states::TRANSIENT) {
+ stringStates->Add(NS_LITERAL_STRING("transient"));
+ }
+ if (state & states::VERTICAL) {
+ stringStates->Add(NS_LITERAL_STRING("vertical"));
+ }
+ if (state & states::STALE) {
+ stringStates->Add(NS_LITERAL_STRING("stale"));
+ }
+ if (state & states::ENABLED) {
+ stringStates->Add(NS_LITERAL_STRING("enabled"));
+ }
+ if (state & states::SENSITIVE) {
+ stringStates->Add(NS_LITERAL_STRING("sensitive"));
+ }
+ if (state & states::EXPANDABLE) {
+ stringStates->Add(NS_LITERAL_STRING("expandable"));
+ }
+
+ //unknown states
+ if (!stringStates->Length()) {
+ stringStates->Add(NS_LITERAL_STRING("unknown"));
+ }
+
+ stringStates.forget(aStringStates);
+}
+
+void
+nsAccessibilityService::GetStringEventType(uint32_t aEventType,
+ nsAString& aString)
+{
+ NS_ASSERTION(nsIAccessibleEvent::EVENT_LAST_ENTRY == ArrayLength(kEventTypeNames),
+ "nsIAccessibleEvent constants are out of sync to kEventTypeNames");
+
+ if (aEventType >= ArrayLength(kEventTypeNames)) {
+ aString.AssignLiteral("unknown");
+ return;
+ }
+
+ CopyUTF8toUTF16(kEventTypeNames[aEventType], aString);
+}
+
+void
+nsAccessibilityService::GetStringRelationType(uint32_t aRelationType,
+ nsAString& aString)
+{
+ NS_ENSURE_TRUE_VOID(aRelationType <= static_cast<uint32_t>(RelationType::LAST));
+
+#define RELATIONTYPE(geckoType, geckoTypeName, atkType, msaaType, ia2Type) \
+ case RelationType::geckoType: \
+ aString.AssignLiteral(geckoTypeName); \
+ return;
+
+ RelationType relationType = static_cast<RelationType>(aRelationType);
+ switch (relationType) {
+#include "RelationTypeMap.h"
+ default:
+ aString.AssignLiteral("unknown");
+ return;
+ }
+
+#undef RELATIONTYPE
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsAccessibilityService public
+
+Accessible*
+nsAccessibilityService::CreateAccessible(nsINode* aNode,
+ Accessible* aContext,
+ bool* aIsSubtreeHidden)
+{
+ MOZ_ASSERT(aContext, "No context provided");
+ MOZ_ASSERT(aNode, "No node to create an accessible for");
+ MOZ_ASSERT(gConsumers, "No creation after shutdown");
+
+ if (aIsSubtreeHidden)
+ *aIsSubtreeHidden = false;
+
+ DocAccessible* document = aContext->Document();
+ MOZ_ASSERT(!document->GetAccessible(aNode),
+ "We already have an accessible for this node.");
+
+ if (aNode->IsNodeOfType(nsINode::eDOCUMENT)) {
+ // If it's document node then ask accessible document loader for
+ // document accessible, otherwise return null.
+ nsCOMPtr<nsIDocument> document(do_QueryInterface(aNode));
+ return GetDocAccessible(document);
+ }
+
+ // We have a content node.
+ if (!aNode->GetComposedDoc()) {
+ NS_WARNING("Creating accessible for node with no document");
+ return nullptr;
+ }
+
+ if (aNode->OwnerDoc() != document->DocumentNode()) {
+ NS_ERROR("Creating accessible for wrong document");
+ return nullptr;
+ }
+
+ if (!aNode->IsContent())
+ return nullptr;
+
+ nsIContent* content = aNode->AsContent();
+ nsIFrame* frame = content->GetPrimaryFrame();
+
+ // Check frame and its visibility. Note, hidden frame allows visible
+ // elements in subtree.
+ if (!frame || !frame->StyleVisibility()->IsVisible()) {
+ if (aIsSubtreeHidden && !frame)
+ *aIsSubtreeHidden = true;
+
+ return nullptr;
+ }
+
+ if (frame->GetContent() != content) {
+ // Not the main content for this frame. This happens because <area>
+ // elements return the image frame as their primary frame. The main content
+ // for the image frame is the image content. If the frame is not an image
+ // frame or the node is not an area element then null is returned.
+ // This setup will change when bug 135040 is fixed. Make sure we don't
+ // create area accessible here. Hopefully assertion below will handle that.
+
+#ifdef DEBUG
+ nsImageFrame* imageFrame = do_QueryFrame(frame);
+ NS_ASSERTION(imageFrame && content->IsHTMLElement(nsGkAtoms::area),
+ "Unknown case of not main content for the frame!");
+#endif
+ return nullptr;
+ }
+
+#ifdef DEBUG
+ nsImageFrame* imageFrame = do_QueryFrame(frame);
+ NS_ASSERTION(!imageFrame || !content->IsHTMLElement(nsGkAtoms::area),
+ "Image map manages the area accessible creation!");
+#endif
+
+ // Attempt to create an accessible based on what we know.
+ RefPtr<Accessible> newAcc;
+
+ // Create accessible for visible text frames.
+ if (content->IsNodeOfType(nsINode::eTEXT)) {
+ nsIFrame::RenderedText text = frame->GetRenderedText(0,
+ UINT32_MAX, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
+ nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
+ // Ignore not rendered text nodes and whitespace text nodes between table
+ // cells.
+ if (text.mString.IsEmpty() ||
+ (aContext->IsTableRow() && nsCoreUtils::IsWhitespaceString(text.mString))) {
+ if (aIsSubtreeHidden)
+ *aIsSubtreeHidden = true;
+
+ return nullptr;
+ }
+
+ newAcc = CreateAccessibleByFrameType(frame, content, aContext);
+ document->BindToDocument(newAcc, nullptr);
+ newAcc->AsTextLeaf()->SetText(text.mString);
+ return newAcc;
+ }
+
+ if (content->IsHTMLElement(nsGkAtoms::map)) {
+ // Create hyper text accessible for HTML map if it is used to group links
+ // (see http://www.w3.org/TR/WCAG10-HTML-TECHS/#group-bypass). If the HTML
+ // map rect is empty then it is used for links grouping. Otherwise it should
+ // be used in conjunction with HTML image element and in this case we don't
+ // create any accessible for it and don't walk into it. The accessibles for
+ // HTML area (HTMLAreaAccessible) the map contains are attached as
+ // children of the appropriate accessible for HTML image
+ // (ImageAccessible).
+ if (nsLayoutUtils::GetAllInFlowRectsUnion(frame,
+ frame->GetParent()).IsEmpty()) {
+ if (aIsSubtreeHidden)
+ *aIsSubtreeHidden = true;
+
+ return nullptr;
+ }
+
+ newAcc = new HyperTextAccessibleWrap(content, document);
+ document->BindToDocument(newAcc, aria::GetRoleMap(content->AsElement()));
+ return newAcc;
+ }
+
+ const nsRoleMapEntry* roleMapEntry = aria::GetRoleMap(content->AsElement());
+
+ // If the element is focusable or global ARIA attribute is applied to it or
+ // it is referenced by ARIA relationship then treat role="presentation" on
+ // the element as the role is not there.
+ if (roleMapEntry &&
+ (roleMapEntry->Is(nsGkAtoms::presentation) ||
+ roleMapEntry->Is(nsGkAtoms::none))) {
+ if (!MustBeAccessible(content, document))
+ return nullptr;
+
+ roleMapEntry = nullptr;
+ }
+
+ if (!newAcc && content->IsHTMLElement()) { // HTML accessibles
+ bool isARIATablePart = roleMapEntry &&
+ (roleMapEntry->accTypes & (eTableCell | eTableRow | eTable));
+
+ if (!isARIATablePart ||
+ frame->AccessibleType() == eHTMLTableCellType ||
+ frame->AccessibleType() == eHTMLTableRowType ||
+ frame->AccessibleType() == eHTMLTableType) {
+ // Prefer to use markup to decide if and what kind of accessible to create,
+ const MarkupMapInfo* markupMap =
+ mMarkupMaps.Get(content->NodeInfo()->NameAtom());
+ if (markupMap && markupMap->new_func)
+ newAcc = markupMap->new_func(content, aContext);
+
+ if (!newAcc) // try by frame accessible type.
+ newAcc = CreateAccessibleByFrameType(frame, content, aContext);
+ }
+
+ // In case of ARIA grid or table use table-specific classes if it's not
+ // native table based.
+ if (isARIATablePart && (!newAcc || newAcc->IsGenericHyperText())) {
+ if ((roleMapEntry->accTypes & eTableCell)) {
+ if (aContext->IsTableRow())
+ newAcc = new ARIAGridCellAccessibleWrap(content, document);
+
+ } else if (roleMapEntry->IsOfType(eTableRow)) {
+ if (aContext->IsTable())
+ newAcc = new ARIARowAccessible(content, document);
+
+ } else if (roleMapEntry->IsOfType(eTable)) {
+ newAcc = new ARIAGridAccessibleWrap(content, document);
+ }
+ }
+
+ // If table has strong ARIA role then all table descendants shouldn't
+ // expose their native roles.
+ if (!roleMapEntry && newAcc && aContext->HasStrongARIARole()) {
+ if (frame->AccessibleType() == eHTMLTableRowType) {
+ const nsRoleMapEntry* contextRoleMap = aContext->ARIARoleMap();
+ if (!contextRoleMap->IsOfType(eTable))
+ roleMapEntry = &aria::gEmptyRoleMap;
+
+ } else if (frame->AccessibleType() == eHTMLTableCellType &&
+ aContext->ARIARoleMap() == &aria::gEmptyRoleMap) {
+ roleMapEntry = &aria::gEmptyRoleMap;
+
+ } else if (content->IsAnyOfHTMLElements(nsGkAtoms::dt,
+ nsGkAtoms::li,
+ nsGkAtoms::dd) ||
+ frame->AccessibleType() == eHTMLLiType) {
+ const nsRoleMapEntry* contextRoleMap = aContext->ARIARoleMap();
+ if (!contextRoleMap->IsOfType(eList))
+ roleMapEntry = &aria::gEmptyRoleMap;
+ }
+ }
+ }
+
+ // Accessible XBL types and deck stuff are used in XUL only currently.
+ if (!newAcc && content->IsXULElement()) {
+ // No accessible for not selected deck panel and its children.
+ if (!aContext->IsXULTabpanels()) {
+ nsDeckFrame* deckFrame = do_QueryFrame(frame->GetParent());
+ if (deckFrame && deckFrame->GetSelectedBox() != frame) {
+ if (aIsSubtreeHidden)
+ *aIsSubtreeHidden = true;
+
+ return nullptr;
+ }
+ }
+
+ // XBL bindings may use @role attribute to point the accessible type
+ // they belong to.
+ newAcc = CreateAccessibleByType(content, document);
+
+ // Any XUL box can be used as tabpanel, make sure we create a proper
+ // accessible for it.
+ if (!newAcc && aContext->IsXULTabpanels() &&
+ content->GetParent() == aContext->GetContent()) {
+ nsIAtom* frameType = frame->GetType();
+ if (frameType == nsGkAtoms::boxFrame ||
+ frameType == nsGkAtoms::scrollFrame) {
+ newAcc = new XULTabpanelAccessible(content, document);
+ }
+ }
+ }
+
+ if (!newAcc) {
+ if (content->IsSVGElement()) {
+ nsSVGPathGeometryFrame* pathGeometryFrame = do_QueryFrame(frame);
+ if (pathGeometryFrame) {
+ // A graphic elements: rect, circle, ellipse, line, path, polygon,
+ // polyline and image. A 'use' and 'text' graphic elements require
+ // special support.
+ newAcc = new EnumRoleAccessible<roles::GRAPHIC>(content, document);
+ } else if (content->IsSVGElement(nsGkAtoms::svg)) {
+ newAcc = new EnumRoleAccessible<roles::DIAGRAM>(content, document);
+ }
+
+ } else if (content->IsMathMLElement()) {
+ const MarkupMapInfo* markupMap =
+ mMarkupMaps.Get(content->NodeInfo()->NameAtom());
+ if (markupMap && markupMap->new_func)
+ newAcc = markupMap->new_func(content, aContext);
+
+ // Fall back to text when encountering Content MathML.
+ if (!newAcc && !content->IsAnyOfMathMLElements(nsGkAtoms::annotation_,
+ nsGkAtoms::annotation_xml_,
+ nsGkAtoms::mpadded_,
+ nsGkAtoms::mphantom_,
+ nsGkAtoms::maligngroup_,
+ nsGkAtoms::malignmark_,
+ nsGkAtoms::mspace_,
+ nsGkAtoms::semantics_)) {
+ newAcc = new HyperTextAccessible(content, document);
+ }
+ }
+ }
+
+ // If no accessible, see if we need to create a generic accessible because
+ // of some property that makes this object interesting
+ // We don't do this for <body>, <html>, <window>, <dialog> etc. which
+ // correspond to the doc accessible and will be created in any case
+ if (!newAcc && !content->IsHTMLElement(nsGkAtoms::body) &&
+ content->GetParent() &&
+ (roleMapEntry || MustBeAccessible(content, document) ||
+ (content->IsHTMLElement() &&
+ nsCoreUtils::HasClickListener(content)))) {
+ // This content is focusable or has an interesting dynamic content accessibility property.
+ // If it's interesting we need it in the accessibility hierarchy so that events or
+ // other accessibles can point to it, or so that it can hold a state, etc.
+ if (content->IsHTMLElement()) {
+ // Interesting HTML container which may have selectable text and/or embedded objects
+ newAcc = new HyperTextAccessibleWrap(content, document);
+ } else { // XUL, SVG, MathML etc.
+ // Interesting generic non-HTML container
+ newAcc = new AccessibleWrap(content, document);
+ }
+ }
+
+ if (newAcc) {
+ document->BindToDocument(newAcc, roleMapEntry);
+ }
+ return newAcc;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsAccessibilityService private
+
+bool
+nsAccessibilityService::Init()
+{
+ // Initialize accessible document manager.
+ if (!DocManager::Init())
+ return false;
+
+ // Add observers.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService)
+ return false;
+
+ observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+
+ static const char16_t kInitIndicator[] = { '1', 0 };
+ observerService->NotifyObservers(nullptr, "a11y-init-or-shutdown", kInitIndicator);
+
+ // Subscribe to EventListenerService.
+ nsCOMPtr<nsIEventListenerService> eventListenerService =
+ do_GetService("@mozilla.org/eventlistenerservice;1");
+ if (!eventListenerService)
+ return false;
+
+ eventListenerService->AddListenerChangeListener(this);
+
+ for (uint32_t i = 0; i < ArrayLength(sMarkupMapList); i++)
+ mMarkupMaps.Put(*sMarkupMapList[i].tag, &sMarkupMapList[i]);
+
+#ifdef A11Y_LOG
+ logging::CheckEnv();
+#endif
+
+ gAccessibilityService = this;
+ NS_ADDREF(gAccessibilityService); // will release in Shutdown()
+
+ if (XRE_IsParentProcess()) {
+ gApplicationAccessible = new ApplicationAccessibleWrap();
+ } else {
+#if defined(XP_WIN)
+ dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
+ MOZ_ASSERT(contentChild);
+ // If we were instantiated by the chrome process, GetMsaaID() will return
+ // a non-zero value and we may safely continue with initialization.
+ if (!contentChild->GetMsaaID()) {
+ // Since we were not instantiated by chrome, we need to synchronously
+ // obtain a MSAA content process id.
+ contentChild->SendGetA11yContentId();
+ }
+#endif // defined(XP_WIN)
+
+ gApplicationAccessible = new ApplicationAccessible();
+ }
+
+ NS_ADDREF(gApplicationAccessible); // will release in Shutdown()
+ gApplicationAccessible->Init();
+
+#ifdef MOZ_CRASHREPORTER
+ CrashReporter::
+ AnnotateCrashReport(NS_LITERAL_CSTRING("Accessibility"),
+ NS_LITERAL_CSTRING("Active"));
+#endif
+
+#ifdef XP_WIN
+ sPendingPlugins = new nsTArray<nsCOMPtr<nsIContent> >;
+ sPluginTimers = new nsTArray<nsCOMPtr<nsITimer> >;
+#endif
+
+ // Now its safe to start platform accessibility.
+ if (XRE_IsParentProcess())
+ PlatformInit();
+
+ statistics::A11yInitialized();
+
+ return true;
+}
+
+void
+nsAccessibilityService::Shutdown()
+{
+ // Application is going to be closed, shutdown accessibility and mark
+ // accessibility service as shutdown to prevent calls of its methods.
+ // Don't null accessibility service static member at this point to be safe
+ // if someone will try to operate with it.
+
+ MOZ_ASSERT(gConsumers, "Accessibility was shutdown already");
+
+ gConsumers = 0;
+
+ // Remove observers.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+
+ static const char16_t kShutdownIndicator[] = { '0', 0 };
+ observerService->NotifyObservers(nullptr, "a11y-init-or-shutdown", kShutdownIndicator);
+ }
+
+ // Stop accessible document loader.
+ DocManager::Shutdown();
+
+ SelectionManager::Shutdown();
+
+#ifdef XP_WIN
+ sPendingPlugins = nullptr;
+
+ uint32_t timerCount = sPluginTimers->Length();
+ for (uint32_t i = 0; i < timerCount; i++)
+ sPluginTimers->ElementAt(i)->Cancel();
+
+ sPluginTimers = nullptr;
+#endif
+
+ if (XRE_IsParentProcess())
+ PlatformShutdown();
+
+ gApplicationAccessible->Shutdown();
+ NS_RELEASE(gApplicationAccessible);
+ gApplicationAccessible = nullptr;
+
+ NS_IF_RELEASE(gXPCApplicationAccessible);
+ gXPCApplicationAccessible = nullptr;
+
+ NS_RELEASE(gAccessibilityService);
+ gAccessibilityService = nullptr;
+}
+
+already_AddRefed<Accessible>
+nsAccessibilityService::CreateAccessibleByType(nsIContent* aContent,
+ DocAccessible* aDoc)
+{
+ nsAutoString role;
+ nsCoreUtils::XBLBindingRole(aContent, role);
+ if (role.IsEmpty() || role.EqualsLiteral("none"))
+ return nullptr;
+
+ if (role.EqualsLiteral("outerdoc")) {
+ RefPtr<Accessible> accessible = new OuterDocAccessible(aContent, aDoc);
+ return accessible.forget();
+ }
+
+ RefPtr<Accessible> accessible;
+#ifdef MOZ_XUL
+ // XUL controls
+ if (role.EqualsLiteral("xul:alert")) {
+ accessible = new XULAlertAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:button")) {
+ accessible = new XULButtonAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:checkbox")) {
+ accessible = new XULCheckboxAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:colorpicker")) {
+ accessible = new XULColorPickerAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:colorpickertile")) {
+ accessible = new XULColorPickerTileAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:combobox")) {
+ accessible = new XULComboboxAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:tabpanels")) {
+ accessible = new XULTabpanelsAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:dropmarker")) {
+ accessible = new XULDropmarkerAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:groupbox")) {
+ accessible = new XULGroupboxAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:image")) {
+ if (aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::onclick)) {
+ accessible = new XULToolbarButtonAccessible(aContent, aDoc);
+
+ } else {
+ // Don't include nameless images in accessible tree.
+ if (!aContent->HasAttr(kNameSpaceID_None,
+ nsGkAtoms::tooltiptext))
+ return nullptr;
+
+ accessible = new ImageAccessibleWrap(aContent, aDoc);
+ }
+
+ } else if (role.EqualsLiteral("xul:link")) {
+ accessible = new XULLinkAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:listbox")) {
+ accessible = new XULListboxAccessibleWrap(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:listcell")) {
+ // Only create cells if there's more than one per row.
+ nsIContent* listItem = aContent->GetParent();
+ if (!listItem)
+ return nullptr;
+
+ for (nsIContent* child = listItem->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsXULElement(nsGkAtoms::listcell) && child != aContent) {
+ accessible = new XULListCellAccessibleWrap(aContent, aDoc);
+ break;
+ }
+ }
+
+ } else if (role.EqualsLiteral("xul:listhead")) {
+ accessible = new XULColumAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:listheader")) {
+ accessible = new XULColumnItemAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:listitem")) {
+ accessible = new XULListitemAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:menubar")) {
+ accessible = new XULMenubarAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:menulist")) {
+ accessible = new XULComboboxAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:menuitem")) {
+ accessible = new XULMenuitemAccessibleWrap(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:menupopup")) {
+#ifdef MOZ_ACCESSIBILITY_ATK
+ // ATK considers this node to be redundant when within menubars, and it makes menu
+ // navigation with assistive technologies more difficult
+ // XXX In the future we will should this for consistency across the nsIAccessible
+ // implementations on each platform for a consistent scripting environment, but
+ // then strip out redundant accessibles in the AccessibleWrap class for each platform.
+ nsIContent *parent = aContent->GetParent();
+ if (parent && parent->IsXULElement(nsGkAtoms::menu))
+ return nullptr;
+#endif
+
+ accessible = new XULMenupopupAccessible(aContent, aDoc);
+
+ } else if(role.EqualsLiteral("xul:menuseparator")) {
+ accessible = new XULMenuSeparatorAccessible(aContent, aDoc);
+
+ } else if(role.EqualsLiteral("xul:pane")) {
+ accessible = new EnumRoleAccessible<roles::PANE>(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:panel")) {
+ if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus,
+ nsGkAtoms::_true, eCaseMatters))
+ accessible = new XULAlertAccessible(aContent, aDoc);
+ else
+ accessible = new EnumRoleAccessible<roles::PANE>(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:progressmeter")) {
+ accessible = new XULProgressMeterAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:statusbar")) {
+ accessible = new XULStatusBarAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:scale")) {
+ accessible = new XULSliderAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:radiobutton")) {
+ accessible = new XULRadioButtonAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:radiogroup")) {
+ accessible = new XULRadioGroupAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:tab")) {
+ accessible = new XULTabAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:tabs")) {
+ accessible = new XULTabsAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:text")) {
+ accessible = new XULLabelAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:textbox")) {
+ accessible = new EnumRoleAccessible<roles::SECTION>(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:thumb")) {
+ accessible = new XULThumbAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:tree")) {
+ accessible = CreateAccessibleForXULTree(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:treecolumns")) {
+ accessible = new XULTreeColumAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:treecolumnitem")) {
+ accessible = new XULColumnItemAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:toolbar")) {
+ accessible = new XULToolbarAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:toolbarseparator")) {
+ accessible = new XULToolbarSeparatorAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:tooltip")) {
+ accessible = new XULTooltipAccessible(aContent, aDoc);
+
+ } else if (role.EqualsLiteral("xul:toolbarbutton")) {
+ accessible = new XULToolbarButtonAccessible(aContent, aDoc);
+
+ }
+#endif // MOZ_XUL
+
+ return accessible.forget();
+}
+
+already_AddRefed<Accessible>
+nsAccessibilityService::CreateAccessibleByFrameType(nsIFrame* aFrame,
+ nsIContent* aContent,
+ Accessible* aContext)
+{
+ DocAccessible* document = aContext->Document();
+
+ RefPtr<Accessible> newAcc;
+ switch (aFrame->AccessibleType()) {
+ case eNoType:
+ return nullptr;
+ case eHTMLBRType:
+ newAcc = new HTMLBRAccessible(aContent, document);
+ break;
+ case eHTMLButtonType:
+ newAcc = new HTMLButtonAccessible(aContent, document);
+ break;
+ case eHTMLCanvasType:
+ newAcc = new HTMLCanvasAccessible(aContent, document);
+ break;
+ case eHTMLCaptionType:
+ if (aContext->IsTable() &&
+ aContext->GetContent() == aContent->GetParent()) {
+ newAcc = new HTMLCaptionAccessible(aContent, document);
+ }
+ break;
+ case eHTMLCheckboxType:
+ newAcc = new HTMLCheckboxAccessible(aContent, document);
+ break;
+ case eHTMLComboboxType:
+ newAcc = new HTMLComboboxAccessible(aContent, document);
+ break;
+ case eHTMLFileInputType:
+ newAcc = new HTMLFileInputAccessible(aContent, document);
+ break;
+ case eHTMLGroupboxType:
+ newAcc = new HTMLGroupboxAccessible(aContent, document);
+ break;
+ case eHTMLHRType:
+ newAcc = new HTMLHRAccessible(aContent, document);
+ break;
+ case eHTMLImageMapType:
+ newAcc = new HTMLImageMapAccessible(aContent, document);
+ break;
+ case eHTMLLiType:
+ if (aContext->IsList() &&
+ aContext->GetContent() == aContent->GetParent()) {
+ newAcc = new HTMLLIAccessible(aContent, document);
+ } else {
+ // Otherwise create a generic text accessible to avoid text jamming.
+ newAcc = new HyperTextAccessibleWrap(aContent, document);
+ }
+ break;
+ case eHTMLSelectListType:
+ newAcc = new HTMLSelectListAccessible(aContent, document);
+ break;
+ case eHTMLMediaType:
+ newAcc = new EnumRoleAccessible<roles::GROUPING>(aContent, document);
+ break;
+ case eHTMLRadioButtonType:
+ newAcc = new HTMLRadioButtonAccessible(aContent, document);
+ break;
+ case eHTMLRangeType:
+ newAcc = new HTMLRangeAccessible(aContent, document);
+ break;
+ case eHTMLSpinnerType:
+ newAcc = new HTMLSpinnerAccessible(aContent, document);
+ break;
+ case eHTMLTableType:
+ if (aContent->IsHTMLElement(nsGkAtoms::table))
+ newAcc = new HTMLTableAccessibleWrap(aContent, document);
+ else
+ newAcc = new HyperTextAccessibleWrap(aContent, document);
+ break;
+ case eHTMLTableCellType:
+ // Accessible HTML table cell should be a child of accessible HTML table
+ // or its row (CSS HTML tables are polite to the used markup at
+ // certain degree).
+ // Otherwise create a generic text accessible to avoid text jamming
+ // when reading by AT.
+ if (aContext->IsHTMLTableRow() || aContext->IsHTMLTable())
+ newAcc = new HTMLTableCellAccessibleWrap(aContent, document);
+ else
+ newAcc = new HyperTextAccessibleWrap(aContent, document);
+ break;
+
+ case eHTMLTableRowType: {
+ // Accessible HTML table row may be a child of tbody/tfoot/thead of
+ // accessible HTML table or a direct child of accessible of HTML table.
+ Accessible* table = aContext->IsTable() ? aContext : nullptr;
+ if (!table && aContext->Parent() && aContext->Parent()->IsTable())
+ table = aContext->Parent();
+
+ if (table) {
+ nsIContent* parentContent = aContent->GetParent();
+ nsIFrame* parentFrame = parentContent->GetPrimaryFrame();
+ if (parentFrame->GetType() != nsGkAtoms::tableWrapperFrame) {
+ parentContent = parentContent->GetParent();
+ parentFrame = parentContent->GetPrimaryFrame();
+ }
+
+ if (parentFrame->GetType() == nsGkAtoms::tableWrapperFrame &&
+ table->GetContent() == parentContent) {
+ newAcc = new HTMLTableRowAccessible(aContent, document);
+ }
+ }
+ break;
+ }
+ case eHTMLTextFieldType:
+ newAcc = new HTMLTextFieldAccessible(aContent, document);
+ break;
+ case eHyperTextType:
+ if (!aContent->IsAnyOfHTMLElements(nsGkAtoms::dt, nsGkAtoms::dd))
+ newAcc = new HyperTextAccessibleWrap(aContent, document);
+ break;
+
+ case eImageType:
+ newAcc = new ImageAccessibleWrap(aContent, document);
+ break;
+ case eOuterDocType:
+ newAcc = new OuterDocAccessible(aContent, document);
+ break;
+ case ePluginType: {
+ nsPluginFrame* pluginFrame = do_QueryFrame(aFrame);
+ newAcc = CreatePluginAccessible(pluginFrame, aContent, aContext);
+ break;
+ }
+ case eTextLeafType:
+ newAcc = new TextLeafAccessibleWrap(aContent, document);
+ break;
+ default:
+ MOZ_ASSERT(false);
+ break;
+ }
+
+ return newAcc.forget();
+}
+
+void
+nsAccessibilityService::MarkupAttributes(const nsIContent* aContent,
+ nsIPersistentProperties* aAttributes) const
+{
+ const mozilla::a11y::MarkupMapInfo* markupMap =
+ mMarkupMaps.Get(aContent->NodeInfo()->NameAtom());
+ if (!markupMap)
+ return;
+
+ for (uint32_t i = 0; i < ArrayLength(markupMap->attrs); i++) {
+ const MarkupAttrInfo* info = markupMap->attrs + i;
+ if (!info->name)
+ break;
+
+ if (info->DOMAttrName) {
+ if (info->DOMAttrValue) {
+ if (aContent->AttrValueIs(kNameSpaceID_None, *info->DOMAttrName,
+ *info->DOMAttrValue, eCaseMatters)) {
+ nsAccUtils::SetAccAttr(aAttributes, *info->name, *info->DOMAttrValue);
+ }
+ continue;
+ }
+
+ nsAutoString value;
+ aContent->GetAttr(kNameSpaceID_None, *info->DOMAttrName, value);
+ if (!value.IsEmpty())
+ nsAccUtils::SetAccAttr(aAttributes, *info->name, value);
+
+ continue;
+ }
+
+ nsAccUtils::SetAccAttr(aAttributes, *info->name, *info->value);
+ }
+}
+
+Accessible*
+nsAccessibilityService::AddNativeRootAccessible(void* aAtkAccessible)
+{
+#ifdef MOZ_ACCESSIBILITY_ATK
+ ApplicationAccessible* applicationAcc = ApplicationAcc();
+ if (!applicationAcc)
+ return nullptr;
+
+ GtkWindowAccessible* nativeWnd =
+ new GtkWindowAccessible(static_cast<AtkObject*>(aAtkAccessible));
+
+ if (applicationAcc->AppendChild(nativeWnd))
+ return nativeWnd;
+#endif
+
+ return nullptr;
+}
+
+void
+nsAccessibilityService::RemoveNativeRootAccessible(Accessible* aAccessible)
+{
+#ifdef MOZ_ACCESSIBILITY_ATK
+ ApplicationAccessible* applicationAcc = ApplicationAcc();
+
+ if (applicationAcc)
+ applicationAcc->RemoveChild(aAccessible);
+#endif
+}
+
+bool
+nsAccessibilityService::HasAccessible(nsIDOMNode* aDOMNode)
+{
+ nsCOMPtr<nsINode> node(do_QueryInterface(aDOMNode));
+ if (!node)
+ return false;
+
+ DocAccessible* document = GetDocAccessible(node->OwnerDoc());
+ if (!document)
+ return false;
+
+ return document->HasAccessible(node);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsAccessibilityService private (DON'T put methods here)
+
+#ifdef MOZ_XUL
+already_AddRefed<Accessible>
+nsAccessibilityService::CreateAccessibleForXULTree(nsIContent* aContent,
+ DocAccessible* aDoc)
+{
+ nsIContent* child = nsTreeUtils::GetDescendantChild(aContent,
+ nsGkAtoms::treechildren);
+ if (!child)
+ return nullptr;
+
+ nsTreeBodyFrame* treeFrame = do_QueryFrame(child->GetPrimaryFrame());
+ if (!treeFrame)
+ return nullptr;
+
+ RefPtr<nsTreeColumns> treeCols = treeFrame->Columns();
+ int32_t count = 0;
+ treeCols->GetCount(&count);
+
+ // Outline of list accessible.
+ if (count == 1) {
+ RefPtr<Accessible> accessible =
+ new XULTreeAccessible(aContent, aDoc, treeFrame);
+ return accessible.forget();
+ }
+
+ // Table or tree table accessible.
+ RefPtr<Accessible> accessible =
+ new XULTreeGridAccessibleWrap(aContent, aDoc, treeFrame);
+ return accessible.forget();
+}
+#endif
+
+nsAccessibilityService*
+GetOrCreateAccService(uint32_t aNewConsumer)
+{
+ if (!nsAccessibilityService::gAccessibilityService) {
+ RefPtr<nsAccessibilityService> service = new nsAccessibilityService();
+ if (!service->Init()) {
+ service->Shutdown();
+ return nullptr;
+ }
+ }
+
+ MOZ_ASSERT(nsAccessibilityService::gAccessibilityService,
+ "Accessible service is not initialized.");
+ nsAccessibilityService::gConsumers |= aNewConsumer;
+ return nsAccessibilityService::gAccessibilityService;
+}
+
+void
+MaybeShutdownAccService(uint32_t aFormerConsumer)
+{
+ nsAccessibilityService* accService =
+ nsAccessibilityService::gAccessibilityService;
+
+ if (!accService || accService->IsShutdown()) {
+ return;
+ }
+
+ if (nsCoreUtils::AccEventObserversExist() ||
+ xpcAccessibilityService::IsInUse()) {
+ // Still used by XPCOM
+ nsAccessibilityService::gConsumers =
+ (nsAccessibilityService::gConsumers & ~aFormerConsumer) |
+ nsAccessibilityService::eXPCOM;
+ return;
+ }
+
+ if (nsAccessibilityService::gConsumers & ~aFormerConsumer) {
+ nsAccessibilityService::gConsumers &= ~aFormerConsumer;
+ } else {
+ accService->Shutdown(); // Will unset all nsAccessibilityService::gConsumers
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Services
+////////////////////////////////////////////////////////////////////////////////
+
+namespace mozilla {
+namespace a11y {
+
+FocusManager*
+FocusMgr()
+{
+ return nsAccessibilityService::gAccessibilityService;
+}
+
+SelectionManager*
+SelectionMgr()
+{
+ return nsAccessibilityService::gAccessibilityService;
+}
+
+ApplicationAccessible*
+ApplicationAcc()
+{
+ return nsAccessibilityService::gApplicationAccessible;
+}
+
+xpcAccessibleApplication*
+XPCApplicationAcc()
+{
+ if (!nsAccessibilityService::gXPCApplicationAccessible &&
+ nsAccessibilityService::gApplicationAccessible) {
+ nsAccessibilityService::gXPCApplicationAccessible =
+ new xpcAccessibleApplication(nsAccessibilityService::gApplicationAccessible);
+ NS_ADDREF(nsAccessibilityService::gXPCApplicationAccessible);
+ }
+
+ return nsAccessibilityService::gXPCApplicationAccessible;
+}
+
+EPlatformDisabledState
+PlatformDisabledState()
+{
+ static int disabledState = 0xff;
+
+ if (disabledState == 0xff) {
+ disabledState = Preferences::GetInt("accessibility.force_disabled", 0);
+ if (disabledState < ePlatformIsForceEnabled)
+ disabledState = ePlatformIsForceEnabled;
+ else if (disabledState > ePlatformIsDisabled)
+ disabledState = ePlatformIsDisabled;
+ }
+
+ return (EPlatformDisabledState)disabledState;
+}
+
+}
+}
diff --git a/accessible/base/nsAccessibilityService.h b/accessible/base/nsAccessibilityService.h
new file mode 100644
index 000000000..562150592
--- /dev/null
+++ b/accessible/base/nsAccessibilityService.h
@@ -0,0 +1,442 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 __nsAccessibilityService_h__
+#define __nsAccessibilityService_h__
+
+#include "mozilla/a11y/DocManager.h"
+#include "mozilla/a11y/FocusManager.h"
+#include "mozilla/a11y/Role.h"
+#include "mozilla/a11y/SelectionManager.h"
+#include "mozilla/Preferences.h"
+
+#include "nsIObserver.h"
+#include "nsIAccessibleEvent.h"
+#include "nsIEventListenerService.h"
+#include "xpcAccessibilityService.h"
+
+class nsImageFrame;
+class nsIArray;
+class nsIPersistentProperties;
+class nsPluginFrame;
+class nsITreeView;
+
+namespace mozilla {
+namespace a11y {
+
+class ApplicationAccessible;
+class xpcAccessibleApplication;
+
+/**
+ * Return focus manager.
+ */
+FocusManager* FocusMgr();
+
+/**
+ * Return selection manager.
+ */
+SelectionManager* SelectionMgr();
+
+/**
+ * Returns the application accessible.
+ */
+ApplicationAccessible* ApplicationAcc();
+xpcAccessibleApplication* XPCApplicationAcc();
+
+typedef Accessible* (New_Accessible)(nsIContent* aContent, Accessible* aContext);
+
+struct MarkupAttrInfo {
+ nsIAtom** name;
+ nsIAtom** value;
+
+ nsIAtom** DOMAttrName;
+ nsIAtom** DOMAttrValue;
+};
+
+struct MarkupMapInfo {
+ nsIAtom** tag;
+ New_Accessible* new_func;
+ a11y::role role;
+ MarkupAttrInfo attrs[4];
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+class nsAccessibilityService final : public mozilla::a11y::DocManager,
+ public mozilla::a11y::FocusManager,
+ public mozilla::a11y::SelectionManager,
+ public nsIListenerChangeListener,
+ public nsIObserver
+{
+public:
+ typedef mozilla::a11y::Accessible Accessible;
+ typedef mozilla::a11y::DocAccessible DocAccessible;
+
+ // nsIListenerChangeListener
+ NS_IMETHOD ListenersChanged(nsIArray* aEventChanges) override;
+
+protected:
+ ~nsAccessibilityService();
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIOBSERVER
+
+ Accessible* GetRootDocumentAccessible(nsIPresShell* aPresShell,
+ bool aCanCreate);
+ already_AddRefed<Accessible>
+ CreatePluginAccessible(nsPluginFrame* aFrame, nsIContent* aContent,
+ Accessible* aContext);
+
+ /**
+ * Adds/remove ATK root accessible for gtk+ native window to/from children
+ * of the application accessible.
+ */
+ Accessible* AddNativeRootAccessible(void* aAtkAccessible);
+ void RemoveNativeRootAccessible(Accessible* aRootAccessible);
+
+ bool HasAccessible(nsIDOMNode* aDOMNode);
+
+ /**
+ * Get a string equivalent for an accessilbe role value.
+ */
+ void GetStringRole(uint32_t aRole, nsAString& aString);
+
+ /**
+ * Get a string equivalent for an accessible state/extra state.
+ */
+ void GetStringStates(uint32_t aState, uint32_t aExtraState,
+ nsISupports **aStringStates);
+
+ /**
+ * Get a string equivalent for an accessible event value.
+ */
+ void GetStringEventType(uint32_t aEventType, nsAString& aString);
+
+ /**
+ * Get a string equivalent for an accessible relation type.
+ */
+ void GetStringRelationType(uint32_t aRelationType, nsAString& aString);
+
+ // nsAccesibilityService
+ /**
+ * Notification used to update the accessible tree when deck panel is
+ * switched.
+ */
+ void DeckPanelSwitched(nsIPresShell* aPresShell, nsIContent* aDeckNode,
+ nsIFrame* aPrevBoxFrame, nsIFrame* aCurrentBoxFrame);
+
+ /**
+ * Notification used to update the accessible tree when new content is
+ * inserted.
+ */
+ void ContentRangeInserted(nsIPresShell* aPresShell, nsIContent* aContainer,
+ nsIContent* aStartChild, nsIContent* aEndChild);
+
+ /**
+ * Notification used to update the accessible tree when content is removed.
+ */
+ void ContentRemoved(nsIPresShell* aPresShell, nsIContent* aChild);
+
+ void UpdateText(nsIPresShell* aPresShell, nsIContent* aContent);
+
+ /**
+ * Update XUL:tree accessible tree when treeview is changed.
+ */
+ void TreeViewChanged(nsIPresShell* aPresShell, nsIContent* aContent,
+ nsITreeView* aView);
+
+ /**
+ * Notify of input@type="element" value change.
+ */
+ void RangeValueChanged(nsIPresShell* aPresShell, nsIContent* aContent);
+
+ /**
+ * Update list bullet accessible.
+ */
+ void UpdateListBullet(nsIPresShell* aPresShell,
+ nsIContent* aHTMLListItemContent,
+ bool aHasBullet);
+
+ /**
+ * Update the image map.
+ */
+ void UpdateImageMap(nsImageFrame* aImageFrame);
+
+ /**
+ * Update the label accessible tree when rendered @value is changed.
+ */
+ void UpdateLabelValue(nsIPresShell* aPresShell, nsIContent* aLabelElm,
+ const nsString& aNewValue);
+
+ /**
+ * Notify accessibility that anchor jump has been accomplished to the given
+ * target. Used by layout.
+ */
+ void NotifyOfAnchorJumpTo(nsIContent *aTarget);
+
+ /**
+ * Notify that presshell is activated.
+ */
+ void PresShellActivated(nsIPresShell* aPresShell);
+
+ /**
+ * Recreate an accessible for the given content node in the presshell.
+ */
+ void RecreateAccessible(nsIPresShell* aPresShell, nsIContent* aContent);
+
+ void FireAccessibleEvent(uint32_t aEvent, Accessible* aTarget);
+
+ // nsAccessibiltiyService
+
+ /**
+ * Return true if accessibility service has been shutdown.
+ */
+ static bool IsShutdown()
+ {
+ return gConsumers == 0;
+ };
+
+ /**
+ * Creates an accessible for the given DOM node.
+ *
+ * @param aNode [in] the given node
+ * @param aContext [in] context the accessible is created in
+ * @param aIsSubtreeHidden [out, optional] indicates whether the node's
+ * frame and its subtree is hidden
+ */
+ Accessible* CreateAccessible(nsINode* aNode, Accessible* aContext,
+ bool* aIsSubtreeHidden = nullptr);
+
+ mozilla::a11y::role MarkupRole(const nsIContent* aContent) const
+ {
+ const mozilla::a11y::MarkupMapInfo* markupMap =
+ mMarkupMaps.Get(aContent->NodeInfo()->NameAtom());
+ return markupMap ? markupMap->role : mozilla::a11y::roles::NOTHING;
+ }
+
+ /**
+ * Set the object attribute defined by markup for the given element.
+ */
+ void MarkupAttributes(const nsIContent* aContent,
+ nsIPersistentProperties* aAttributes) const;
+
+ /**
+ * A list of possible accessibility service consumers. Accessibility service
+ * can only be shut down when there are no remaining consumers.
+ *
+ * eXPCOM - accessibility service is used by XPCOM.
+ *
+ * eMainProcess - accessibility service was started by main process in the
+ * content process.
+ *
+ * ePlatformAPI - accessibility service is used by the platform api in the
+ * main process.
+ */
+ enum ServiceConsumer
+ {
+ eXPCOM = 1 << 0,
+ eMainProcess = 1 << 1,
+ ePlatformAPI = 1 << 2,
+ };
+
+private:
+ // nsAccessibilityService creation is controlled by friend
+ // GetOrCreateAccService, keep constructors private.
+ nsAccessibilityService();
+ nsAccessibilityService(const nsAccessibilityService&);
+ nsAccessibilityService& operator =(const nsAccessibilityService&);
+
+private:
+ /**
+ * Initialize accessibility service.
+ */
+ bool Init();
+
+ /**
+ * Shutdowns accessibility service.
+ */
+ void Shutdown();
+
+ /**
+ * Create accessible for the element having XBL bindings.
+ */
+ already_AddRefed<Accessible>
+ CreateAccessibleByType(nsIContent* aContent, DocAccessible* aDoc);
+
+ /**
+ * Create an accessible whose type depends on the given frame.
+ */
+ already_AddRefed<Accessible>
+ CreateAccessibleByFrameType(nsIFrame* aFrame, nsIContent* aContent,
+ Accessible* aContext);
+
+#ifdef MOZ_XUL
+ /**
+ * Create accessible for XUL tree element.
+ */
+ already_AddRefed<Accessible>
+ CreateAccessibleForXULTree(nsIContent* aContent, DocAccessible* aDoc);
+#endif
+
+ /**
+ * Reference for accessibility service instance.
+ */
+ static nsAccessibilityService* gAccessibilityService;
+
+ /**
+ * Reference for application accessible instance.
+ */
+ static mozilla::a11y::ApplicationAccessible* gApplicationAccessible;
+ static mozilla::a11y::xpcAccessibleApplication* gXPCApplicationAccessible;
+
+ /**
+ * Contains a set of accessibility service consumers.
+ */
+ static uint32_t gConsumers;
+
+ nsDataHashtable<nsPtrHashKey<const nsIAtom>, const mozilla::a11y::MarkupMapInfo*> mMarkupMaps;
+
+ friend nsAccessibilityService* GetAccService();
+ friend nsAccessibilityService* GetOrCreateAccService(uint32_t);
+ friend void MaybeShutdownAccService(uint32_t);
+ friend mozilla::a11y::FocusManager* mozilla::a11y::FocusMgr();
+ friend mozilla::a11y::SelectionManager* mozilla::a11y::SelectionMgr();
+ friend mozilla::a11y::ApplicationAccessible* mozilla::a11y::ApplicationAcc();
+ friend mozilla::a11y::xpcAccessibleApplication* mozilla::a11y::XPCApplicationAcc();
+ friend class xpcAccessibilityService;
+};
+
+/**
+ * Return the accessibility service instance. (Handy global function)
+ */
+inline nsAccessibilityService*
+GetAccService()
+{
+ return nsAccessibilityService::gAccessibilityService;
+}
+
+/**
+ * Return accessibility service instance; creating one if necessary.
+ */
+nsAccessibilityService* GetOrCreateAccService(
+ uint32_t aNewConsumer = nsAccessibilityService::ePlatformAPI);
+
+/**
+ * Shutdown accessibility service if needed.
+ */
+void MaybeShutdownAccService(uint32_t aFormerConsumer);
+
+/**
+ * Return true if we're in a content process and not B2G.
+ */
+inline bool
+IPCAccessibilityActive()
+{
+#ifdef MOZ_B2G
+ return false;
+#else
+ return XRE_IsContentProcess() &&
+ mozilla::Preferences::GetBool("accessibility.ipc_architecture.enabled", true);
+#endif
+}
+
+/**
+ * Map nsIAccessibleEvents constants to strings. Used by
+ * nsAccessibilityService::GetStringEventType() method.
+ */
+static const char kEventTypeNames[][40] = {
+ "unknown", //
+ "show", // EVENT_SHOW
+ "hide", // EVENT_HIDE
+ "reorder", // EVENT_REORDER
+ "active decendent change", // EVENT_ACTIVE_DECENDENT_CHANGED
+ "focus", // EVENT_FOCUS
+ "state change", // EVENT_STATE_CHANGE
+ "location change", // EVENT_LOCATION_CHANGE
+ "name changed", // EVENT_NAME_CHANGE
+ "description change", // EVENT_DESCRIPTION_CHANGE
+ "value change", // EVENT_VALUE_CHANGE
+ "help change", // EVENT_HELP_CHANGE
+ "default action change", // EVENT_DEFACTION_CHANGE
+ "action change", // EVENT_ACTION_CHANGE
+ "accelerator change", // EVENT_ACCELERATOR_CHANGE
+ "selection", // EVENT_SELECTION
+ "selection add", // EVENT_SELECTION_ADD
+ "selection remove", // EVENT_SELECTION_REMOVE
+ "selection within", // EVENT_SELECTION_WITHIN
+ "alert", // EVENT_ALERT
+ "foreground", // EVENT_FOREGROUND
+ "menu start", // EVENT_MENU_START
+ "menu end", // EVENT_MENU_END
+ "menupopup start", // EVENT_MENUPOPUP_START
+ "menupopup end", // EVENT_MENUPOPUP_END
+ "capture start", // EVENT_CAPTURE_START
+ "capture end", // EVENT_CAPTURE_END
+ "movesize start", // EVENT_MOVESIZE_START
+ "movesize end", // EVENT_MOVESIZE_END
+ "contexthelp start", // EVENT_CONTEXTHELP_START
+ "contexthelp end", // EVENT_CONTEXTHELP_END
+ "dragdrop start", // EVENT_DRAGDROP_START
+ "dragdrop end", // EVENT_DRAGDROP_END
+ "dialog start", // EVENT_DIALOG_START
+ "dialog end", // EVENT_DIALOG_END
+ "scrolling start", // EVENT_SCROLLING_START
+ "scrolling end", // EVENT_SCROLLING_END
+ "minimize start", // EVENT_MINIMIZE_START
+ "minimize end", // EVENT_MINIMIZE_END
+ "document load complete", // EVENT_DOCUMENT_LOAD_COMPLETE
+ "document reload", // EVENT_DOCUMENT_RELOAD
+ "document load stopped", // EVENT_DOCUMENT_LOAD_STOPPED
+ "document attributes changed", // EVENT_DOCUMENT_ATTRIBUTES_CHANGED
+ "document content changed", // EVENT_DOCUMENT_CONTENT_CHANGED
+ "property changed", // EVENT_PROPERTY_CHANGED
+ "page changed", // EVENT_PAGE_CHANGED
+ "text attribute changed", // EVENT_TEXT_ATTRIBUTE_CHANGED
+ "text caret moved", // EVENT_TEXT_CARET_MOVED
+ "text changed", // EVENT_TEXT_CHANGED
+ "text inserted", // EVENT_TEXT_INSERTED
+ "text removed", // EVENT_TEXT_REMOVED
+ "text updated", // EVENT_TEXT_UPDATED
+ "text selection changed", // EVENT_TEXT_SELECTION_CHANGED
+ "visible data changed", // EVENT_VISIBLE_DATA_CHANGED
+ "text column changed", // EVENT_TEXT_COLUMN_CHANGED
+ "section changed", // EVENT_SECTION_CHANGED
+ "table caption changed", // EVENT_TABLE_CAPTION_CHANGED
+ "table model changed", // EVENT_TABLE_MODEL_CHANGED
+ "table summary changed", // EVENT_TABLE_SUMMARY_CHANGED
+ "table row description changed", // EVENT_TABLE_ROW_DESCRIPTION_CHANGED
+ "table row header changed", // EVENT_TABLE_ROW_HEADER_CHANGED
+ "table row insert", // EVENT_TABLE_ROW_INSERT
+ "table row delete", // EVENT_TABLE_ROW_DELETE
+ "table row reorder", // EVENT_TABLE_ROW_REORDER
+ "table column description changed", // EVENT_TABLE_COLUMN_DESCRIPTION_CHANGED
+ "table column header changed", // EVENT_TABLE_COLUMN_HEADER_CHANGED
+ "table column insert", // EVENT_TABLE_COLUMN_INSERT
+ "table column delete", // EVENT_TABLE_COLUMN_DELETE
+ "table column reorder", // EVENT_TABLE_COLUMN_REORDER
+ "window activate", // EVENT_WINDOW_ACTIVATE
+ "window create", // EVENT_WINDOW_CREATE
+ "window deactivate", // EVENT_WINDOW_DEACTIVATE
+ "window destroy", // EVENT_WINDOW_DESTROY
+ "window maximize", // EVENT_WINDOW_MAXIMIZE
+ "window minimize", // EVENT_WINDOW_MINIMIZE
+ "window resize", // EVENT_WINDOW_RESIZE
+ "window restore", // EVENT_WINDOW_RESTORE
+ "hyperlink end index changed", // EVENT_HYPERLINK_END_INDEX_CHANGED
+ "hyperlink number of anchors changed", // EVENT_HYPERLINK_NUMBER_OF_ANCHORS_CHANGED
+ "hyperlink selected link changed", // EVENT_HYPERLINK_SELECTED_LINK_CHANGED
+ "hypertext link activated", // EVENT_HYPERTEXT_LINK_ACTIVATED
+ "hypertext link selected", // EVENT_HYPERTEXT_LINK_SELECTED
+ "hyperlink start index changed", // EVENT_HYPERLINK_START_INDEX_CHANGED
+ "hypertext changed", // EVENT_HYPERTEXT_CHANGED
+ "hypertext links count changed", // EVENT_HYPERTEXT_NLINKS_CHANGED
+ "object attribute changed", // EVENT_OBJECT_ATTRIBUTE_CHANGED
+ "virtual cursor changed", // EVENT_VIRTUALCURSOR_CHANGED
+ "text value change", // EVENT_TEXT_VALUE_CHANGE
+};
+
+#endif
diff --git a/accessible/base/nsAccessiblePivot.cpp b/accessible/base/nsAccessiblePivot.cpp
new file mode 100644
index 000000000..4a6b9f850
--- /dev/null
+++ b/accessible/base/nsAccessiblePivot.cpp
@@ -0,0 +1,924 @@
+/* -*- 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 "nsAccessiblePivot.h"
+
+#include "HyperTextAccessible.h"
+#include "nsAccUtils.h"
+#include "States.h"
+#include "xpcAccessibleDocument.h"
+
+using namespace mozilla::a11y;
+
+
+/**
+ * An object that stores a given traversal rule during the pivot movement.
+ */
+class RuleCache
+{
+public:
+ explicit RuleCache(nsIAccessibleTraversalRule* aRule) : mRule(aRule),
+ mAcceptRoles(nullptr) { }
+ ~RuleCache () {
+ if (mAcceptRoles)
+ free(mAcceptRoles);
+ }
+
+ nsresult ApplyFilter(Accessible* aAccessible, uint16_t* aResult);
+
+private:
+ nsCOMPtr<nsIAccessibleTraversalRule> mRule;
+ uint32_t* mAcceptRoles;
+ uint32_t mAcceptRolesLength;
+ uint32_t mPreFilter;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// nsAccessiblePivot
+
+nsAccessiblePivot::nsAccessiblePivot(Accessible* aRoot) :
+ mRoot(aRoot), mModalRoot(nullptr), mPosition(nullptr),
+ mStartOffset(-1), mEndOffset(-1)
+{
+ NS_ASSERTION(aRoot, "A root accessible is required");
+}
+
+nsAccessiblePivot::~nsAccessiblePivot()
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports
+
+NS_IMPL_CYCLE_COLLECTION(nsAccessiblePivot, mRoot, mPosition, mObservers)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsAccessiblePivot)
+ NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivot)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAccessiblePivot)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsAccessiblePivot)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsAccessiblePivot)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIAccessiblePivot
+
+NS_IMETHODIMP
+nsAccessiblePivot::GetRoot(nsIAccessible** aRoot)
+{
+ NS_ENSURE_ARG_POINTER(aRoot);
+
+ NS_IF_ADDREF(*aRoot = ToXPC(mRoot));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::GetPosition(nsIAccessible** aPosition)
+{
+ NS_ENSURE_ARG_POINTER(aPosition);
+
+ NS_IF_ADDREF(*aPosition = ToXPC(mPosition));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::SetPosition(nsIAccessible* aPosition)
+{
+ RefPtr<Accessible> position = nullptr;
+
+ if (aPosition) {
+ position = aPosition->ToInternalAccessible();
+ if (!position || !IsDescendantOf(position, GetActiveRoot()))
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Swap old position with new position, saves us an AddRef/Release.
+ mPosition.swap(position);
+ int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
+ mStartOffset = mEndOffset = -1;
+ NotifyOfPivotChange(position, oldStart, oldEnd,
+ nsIAccessiblePivot::REASON_NONE, false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::GetModalRoot(nsIAccessible** aModalRoot)
+{
+ NS_ENSURE_ARG_POINTER(aModalRoot);
+
+ NS_IF_ADDREF(*aModalRoot = ToXPC(mModalRoot));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::SetModalRoot(nsIAccessible* aModalRoot)
+{
+ Accessible* modalRoot = nullptr;
+
+ if (aModalRoot) {
+ modalRoot = aModalRoot->ToInternalAccessible();
+ if (!modalRoot || !IsDescendantOf(modalRoot, mRoot))
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mModalRoot = modalRoot;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::GetStartOffset(int32_t* aStartOffset)
+{
+ NS_ENSURE_ARG_POINTER(aStartOffset);
+
+ *aStartOffset = mStartOffset;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::GetEndOffset(int32_t* aEndOffset)
+{
+ NS_ENSURE_ARG_POINTER(aEndOffset);
+
+ *aEndOffset = mEndOffset;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::SetTextRange(nsIAccessibleText* aTextAccessible,
+ int32_t aStartOffset, int32_t aEndOffset,
+ bool aIsFromUserInput, uint8_t aArgc)
+{
+ NS_ENSURE_ARG(aTextAccessible);
+
+ // Check that start offset is smaller than end offset, and that if a value is
+ // smaller than 0, both should be -1.
+ NS_ENSURE_TRUE(aStartOffset <= aEndOffset &&
+ (aStartOffset >= 0 || (aStartOffset != -1 && aEndOffset != -1)),
+ NS_ERROR_INVALID_ARG);
+
+ nsCOMPtr<nsIAccessible> xpcAcc = do_QueryInterface(aTextAccessible);
+ NS_ENSURE_ARG(xpcAcc);
+
+ RefPtr<Accessible> acc = xpcAcc->ToInternalAccessible();
+ NS_ENSURE_ARG(acc);
+
+ HyperTextAccessible* position = acc->AsHyperText();
+ if (!position || !IsDescendantOf(position, GetActiveRoot()))
+ return NS_ERROR_INVALID_ARG;
+
+ // Make sure the given offsets don't exceed the character count.
+ if (aEndOffset > static_cast<int32_t>(position->CharacterCount()))
+ return NS_ERROR_FAILURE;
+
+ int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
+ mStartOffset = aStartOffset;
+ mEndOffset = aEndOffset;
+
+ mPosition.swap(acc);
+ NotifyOfPivotChange(acc, oldStart, oldEnd,
+ nsIAccessiblePivot::REASON_TEXT,
+ (aArgc > 0) ? aIsFromUserInput : true);
+
+ return NS_OK;
+}
+
+// Traversal functions
+
+NS_IMETHODIMP
+nsAccessiblePivot::MoveNext(nsIAccessibleTraversalRule* aRule,
+ nsIAccessible* aAnchor, bool aIncludeStart,
+ bool aIsFromUserInput, uint8_t aArgc, bool* aResult)
+{
+ NS_ENSURE_ARG(aResult);
+ NS_ENSURE_ARG(aRule);
+ *aResult = false;
+
+ Accessible* anchor = mPosition;
+ if (aArgc > 0 && aAnchor)
+ anchor = aAnchor->ToInternalAccessible();
+
+ if (anchor && (anchor->IsDefunct() || !IsDescendantOf(anchor, GetActiveRoot())))
+ return NS_ERROR_NOT_IN_TREE;
+
+ nsresult rv = NS_OK;
+ Accessible* accessible =
+ SearchForward(anchor, aRule, (aArgc > 1) ? aIncludeStart : false, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (accessible)
+ *aResult = MovePivotInternal(accessible, nsIAccessiblePivot::REASON_NEXT,
+ (aArgc > 2) ? aIsFromUserInput : true);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::MovePrevious(nsIAccessibleTraversalRule* aRule,
+ nsIAccessible* aAnchor,
+ bool aIncludeStart, bool aIsFromUserInput,
+ uint8_t aArgc, bool* aResult)
+{
+ NS_ENSURE_ARG(aResult);
+ NS_ENSURE_ARG(aRule);
+ *aResult = false;
+
+ Accessible* anchor = mPosition;
+ if (aArgc > 0 && aAnchor)
+ anchor = aAnchor->ToInternalAccessible();
+
+ if (anchor && (anchor->IsDefunct() || !IsDescendantOf(anchor, GetActiveRoot())))
+ return NS_ERROR_NOT_IN_TREE;
+
+ nsresult rv = NS_OK;
+ Accessible* accessible =
+ SearchBackward(anchor, aRule, (aArgc > 1) ? aIncludeStart : false, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (accessible)
+ *aResult = MovePivotInternal(accessible, nsIAccessiblePivot::REASON_PREV,
+ (aArgc > 2) ? aIsFromUserInput : true);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::MoveFirst(nsIAccessibleTraversalRule* aRule,
+ bool aIsFromUserInput,
+ uint8_t aArgc, bool* aResult)
+{
+ NS_ENSURE_ARG(aResult);
+ NS_ENSURE_ARG(aRule);
+
+ Accessible* root = GetActiveRoot();
+ NS_ENSURE_TRUE(root && !root->IsDefunct(), NS_ERROR_NOT_IN_TREE);
+
+ nsresult rv = NS_OK;
+ Accessible* accessible = SearchForward(root, aRule, true, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (accessible)
+ *aResult = MovePivotInternal(accessible, nsIAccessiblePivot::REASON_FIRST,
+ (aArgc > 0) ? aIsFromUserInput : true);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::MoveLast(nsIAccessibleTraversalRule* aRule,
+ bool aIsFromUserInput,
+ uint8_t aArgc, bool* aResult)
+{
+ NS_ENSURE_ARG(aResult);
+ NS_ENSURE_ARG(aRule);
+
+ Accessible* root = GetActiveRoot();
+ NS_ENSURE_TRUE(root && !root->IsDefunct(), NS_ERROR_NOT_IN_TREE);
+
+ *aResult = false;
+ nsresult rv = NS_OK;
+ Accessible* lastAccessible = root;
+ Accessible* accessible = nullptr;
+
+ // First go to the last accessible in pre-order
+ while (lastAccessible->HasChildren())
+ lastAccessible = lastAccessible->LastChild();
+
+ // Search backwards from last accessible and find the last occurrence in the doc
+ accessible = SearchBackward(lastAccessible, aRule, true, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (accessible)
+ *aResult = MovePivotInternal(accessible, nsAccessiblePivot::REASON_LAST,
+ (aArgc > 0) ? aIsFromUserInput : true);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::MoveNextByText(TextBoundaryType aBoundary,
+ bool aIsFromUserInput, uint8_t aArgc,
+ bool* aResult)
+{
+ NS_ENSURE_ARG(aResult);
+
+ *aResult = false;
+
+ int32_t tempStart = mStartOffset, tempEnd = mEndOffset;
+ Accessible* tempPosition = mPosition;
+ Accessible* root = GetActiveRoot();
+ while (true) {
+ Accessible* curPosition = tempPosition;
+ HyperTextAccessible* text = nullptr;
+ // Find the nearest text node using a preorder traversal starting from
+ // the current node.
+ if (!(text = tempPosition->AsHyperText())) {
+ text = SearchForText(tempPosition, false);
+ if (!text)
+ return NS_OK;
+ if (text != curPosition)
+ tempStart = tempEnd = -1;
+ tempPosition = text;
+ }
+
+ // If the search led to the parent of the node we started on (e.g. when
+ // starting on a text leaf), start the text movement from the end of that
+ // node, otherwise we just default to 0.
+ if (tempEnd == -1)
+ tempEnd = text == curPosition->Parent() ?
+ text->GetChildOffset(curPosition) : 0;
+
+ // If there's no more text on the current node, try to find the next text
+ // node; if there isn't one, bail out.
+ if (tempEnd == static_cast<int32_t>(text->CharacterCount())) {
+ if (tempPosition == root)
+ return NS_OK;
+
+ // If we're currently sitting on a link, try move to either the next
+ // sibling or the parent, whichever is closer to the current end
+ // offset. Otherwise, do a forward search for the next node to land on
+ // (we don't do this in the first case because we don't want to go to the
+ // subtree).
+ Accessible* sibling = tempPosition->NextSibling();
+ if (tempPosition->IsLink()) {
+ if (sibling && sibling->IsLink()) {
+ tempStart = tempEnd = -1;
+ tempPosition = sibling;
+ } else {
+ tempStart = tempPosition->StartOffset();
+ tempEnd = tempPosition->EndOffset();
+ tempPosition = tempPosition->Parent();
+ }
+ } else {
+ tempPosition = SearchForText(tempPosition, false);
+ if (!tempPosition)
+ return NS_OK;
+ tempStart = tempEnd = -1;
+ }
+ continue;
+ }
+
+ AccessibleTextBoundary startBoundary, endBoundary;
+ switch (aBoundary) {
+ case CHAR_BOUNDARY:
+ startBoundary = nsIAccessibleText::BOUNDARY_CHAR;
+ endBoundary = nsIAccessibleText::BOUNDARY_CHAR;
+ break;
+ case WORD_BOUNDARY:
+ startBoundary = nsIAccessibleText::BOUNDARY_WORD_START;
+ endBoundary = nsIAccessibleText::BOUNDARY_WORD_END;
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsAutoString unusedText;
+ int32_t newStart = 0, newEnd = 0, currentEnd = tempEnd;
+ text->TextAtOffset(tempEnd, endBoundary, &newStart, &tempEnd, unusedText);
+ text->TextBeforeOffset(tempEnd, startBoundary, &newStart, &newEnd, unusedText);
+ int32_t potentialStart = newEnd == tempEnd ? newStart : newEnd;
+ tempStart = potentialStart > tempStart ? potentialStart : currentEnd;
+
+ // The offset range we've obtained might have embedded characters in it,
+ // limit the range to the start of the first occurrence of an embedded
+ // character.
+ Accessible* childAtOffset = nullptr;
+ for (int32_t i = tempStart; i < tempEnd; i++) {
+ childAtOffset = text->GetChildAtOffset(i);
+ if (childAtOffset && !childAtOffset->IsText()) {
+ tempEnd = i;
+ break;
+ }
+ }
+ // If there's an embedded character at the very start of the range, we
+ // instead want to traverse into it. So restart the movement with
+ // the child as the starting point.
+ if (childAtOffset && !childAtOffset->IsText() &&
+ tempStart == static_cast<int32_t>(childAtOffset->StartOffset())) {
+ tempPosition = childAtOffset;
+ tempStart = tempEnd = -1;
+ continue;
+ }
+
+ *aResult = true;
+
+ Accessible* startPosition = mPosition;
+ int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
+ mPosition = tempPosition;
+ mStartOffset = tempStart;
+ mEndOffset = tempEnd;
+ NotifyOfPivotChange(startPosition, oldStart, oldEnd,
+ nsIAccessiblePivot::REASON_TEXT,
+ (aArgc > 0) ? aIsFromUserInput : true);
+ return NS_OK;
+ }
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::MovePreviousByText(TextBoundaryType aBoundary,
+ bool aIsFromUserInput, uint8_t aArgc,
+ bool* aResult)
+{
+ NS_ENSURE_ARG(aResult);
+
+ *aResult = false;
+
+ int32_t tempStart = mStartOffset, tempEnd = mEndOffset;
+ Accessible* tempPosition = mPosition;
+ Accessible* root = GetActiveRoot();
+ while (true) {
+ Accessible* curPosition = tempPosition;
+ HyperTextAccessible* text;
+ // Find the nearest text node using a reverse preorder traversal starting
+ // from the current node.
+ if (!(text = tempPosition->AsHyperText())) {
+ text = SearchForText(tempPosition, true);
+ if (!text)
+ return NS_OK;
+ if (text != curPosition)
+ tempStart = tempEnd = -1;
+ tempPosition = text;
+ }
+
+ // If the search led to the parent of the node we started on (e.g. when
+ // starting on a text leaf), start the text movement from the end of that
+ // node, otherwise we just default to 0.
+ if (tempStart == -1) {
+ if (tempPosition != curPosition)
+ tempStart = text == curPosition->Parent() ?
+ text->GetChildOffset(curPosition) : text->CharacterCount();
+ else
+ tempStart = 0;
+ }
+
+ // If there's no more text on the current node, try to find the previous
+ // text node; if there isn't one, bail out.
+ if (tempStart == 0) {
+ if (tempPosition == root)
+ return NS_OK;
+
+ // If we're currently sitting on a link, try move to either the previous
+ // sibling or the parent, whichever is closer to the current end
+ // offset. Otherwise, do a forward search for the next node to land on
+ // (we don't do this in the first case because we don't want to go to the
+ // subtree).
+ Accessible* sibling = tempPosition->PrevSibling();
+ if (tempPosition->IsLink()) {
+ if (sibling && sibling->IsLink()) {
+ HyperTextAccessible* siblingText = sibling->AsHyperText();
+ tempStart = tempEnd = siblingText ?
+ siblingText->CharacterCount() : -1;
+ tempPosition = sibling;
+ } else {
+ tempStart = tempPosition->StartOffset();
+ tempEnd = tempPosition->EndOffset();
+ tempPosition = tempPosition->Parent();
+ }
+ } else {
+ HyperTextAccessible* tempText = SearchForText(tempPosition, true);
+ if (!tempText)
+ return NS_OK;
+ tempPosition = tempText;
+ tempStart = tempEnd = tempText->CharacterCount();
+ }
+ continue;
+ }
+
+ AccessibleTextBoundary startBoundary, endBoundary;
+ switch (aBoundary) {
+ case CHAR_BOUNDARY:
+ startBoundary = nsIAccessibleText::BOUNDARY_CHAR;
+ endBoundary = nsIAccessibleText::BOUNDARY_CHAR;
+ break;
+ case WORD_BOUNDARY:
+ startBoundary = nsIAccessibleText::BOUNDARY_WORD_START;
+ endBoundary = nsIAccessibleText::BOUNDARY_WORD_END;
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsAutoString unusedText;
+ int32_t newStart = 0, newEnd = 0, currentStart = tempStart, potentialEnd = 0;
+ text->TextBeforeOffset(tempStart, startBoundary, &newStart, &newEnd, unusedText);
+ if (newStart < tempStart)
+ tempStart = newEnd >= currentStart ? newStart : newEnd;
+ else // XXX: In certain odd cases newStart is equal to tempStart
+ text->TextBeforeOffset(tempStart - 1, startBoundary, &newStart,
+ &tempStart, unusedText);
+ text->TextAtOffset(tempStart, endBoundary, &newStart, &potentialEnd,
+ unusedText);
+ tempEnd = potentialEnd < tempEnd ? potentialEnd : currentStart;
+
+ // The offset range we've obtained might have embedded characters in it,
+ // limit the range to the start of the last occurrence of an embedded
+ // character.
+ Accessible* childAtOffset = nullptr;
+ for (int32_t i = tempEnd - 1; i >= tempStart; i--) {
+ childAtOffset = text->GetChildAtOffset(i);
+ if (childAtOffset && !childAtOffset->IsText()) {
+ tempStart = childAtOffset->EndOffset();
+ break;
+ }
+ }
+ // If there's an embedded character at the very end of the range, we
+ // instead want to traverse into it. So restart the movement with
+ // the child as the starting point.
+ if (childAtOffset && !childAtOffset->IsText() &&
+ tempEnd == static_cast<int32_t>(childAtOffset->EndOffset())) {
+ tempPosition = childAtOffset;
+ tempStart = tempEnd = childAtOffset->AsHyperText()->CharacterCount();
+ continue;
+ }
+
+ *aResult = true;
+
+ Accessible* startPosition = mPosition;
+ int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
+ mPosition = tempPosition;
+ mStartOffset = tempStart;
+ mEndOffset = tempEnd;
+
+ NotifyOfPivotChange(startPosition, oldStart, oldEnd,
+ nsIAccessiblePivot::REASON_TEXT,
+ (aArgc > 0) ? aIsFromUserInput : true);
+ return NS_OK;
+ }
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::MoveToPoint(nsIAccessibleTraversalRule* aRule,
+ int32_t aX, int32_t aY, bool aIgnoreNoMatch,
+ bool aIsFromUserInput, uint8_t aArgc,
+ bool* aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_ARG_POINTER(aRule);
+
+ *aResult = false;
+
+ Accessible* root = GetActiveRoot();
+ NS_ENSURE_TRUE(root && !root->IsDefunct(), NS_ERROR_NOT_IN_TREE);
+
+ RuleCache cache(aRule);
+ Accessible* match = nullptr;
+ Accessible* child = root->ChildAtPoint(aX, aY, Accessible::eDeepestChild);
+ while (child && root != child) {
+ uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
+ nsresult rv = cache.ApplyFilter(child, &filtered);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Ignore any matching nodes that were below this one
+ if (filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE)
+ match = nullptr;
+
+ // Match if no node below this is a match
+ if ((filtered & nsIAccessibleTraversalRule::FILTER_MATCH) && !match) {
+ nsIntRect childRect = child->Bounds();
+ // Double-check child's bounds since the deepest child may have been out
+ // of bounds. This assures we don't return a false positive.
+ if (aX >= childRect.x && aX < childRect.x + childRect.width &&
+ aY >= childRect.y && aY < childRect.y + childRect.height)
+ match = child;
+ }
+
+ child = child->Parent();
+ }
+
+ if (match || !aIgnoreNoMatch)
+ *aResult = MovePivotInternal(match, nsIAccessiblePivot::REASON_POINT,
+ (aArgc > 0) ? aIsFromUserInput : true);
+
+ return NS_OK;
+}
+
+// Observer functions
+
+NS_IMETHODIMP
+nsAccessiblePivot::AddObserver(nsIAccessiblePivotObserver* aObserver)
+{
+ NS_ENSURE_ARG(aObserver);
+
+ mObservers.AppendElement(aObserver);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessiblePivot::RemoveObserver(nsIAccessiblePivotObserver* aObserver)
+{
+ NS_ENSURE_ARG(aObserver);
+
+ return mObservers.RemoveElement(aObserver) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+// Private utility methods
+
+bool
+nsAccessiblePivot::IsDescendantOf(Accessible* aAccessible, Accessible* aAncestor)
+{
+ if (!aAncestor || aAncestor->IsDefunct())
+ return false;
+
+ // XXX Optimize with IsInDocument() when appropriate. Blocked by bug 759875.
+ Accessible* accessible = aAccessible;
+ do {
+ if (accessible == aAncestor)
+ return true;
+ } while ((accessible = accessible->Parent()));
+
+ return false;
+}
+
+bool
+nsAccessiblePivot::MovePivotInternal(Accessible* aPosition,
+ PivotMoveReason aReason,
+ bool aIsFromUserInput)
+{
+ RefPtr<Accessible> oldPosition = mPosition.forget();
+ mPosition = aPosition;
+ int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
+ mStartOffset = mEndOffset = -1;
+
+ return NotifyOfPivotChange(oldPosition, oldStart, oldEnd, aReason,
+ aIsFromUserInput);
+}
+
+Accessible*
+nsAccessiblePivot::AdjustStartPosition(Accessible* aAccessible,
+ RuleCache& aCache,
+ uint16_t* aFilterResult,
+ nsresult* aResult)
+{
+ Accessible* matched = aAccessible;
+ *aResult = aCache.ApplyFilter(aAccessible, aFilterResult);
+
+ if (aAccessible != mRoot && aAccessible != mModalRoot) {
+ for (Accessible* temp = aAccessible->Parent();
+ temp && temp != mRoot && temp != mModalRoot; temp = temp->Parent()) {
+ uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
+ *aResult = aCache.ApplyFilter(temp, &filtered);
+ NS_ENSURE_SUCCESS(*aResult, nullptr);
+ if (filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) {
+ *aFilterResult = filtered;
+ matched = temp;
+ }
+ }
+ }
+
+ if (aAccessible == mPosition && mStartOffset != -1 && mEndOffset != -1) {
+ HyperTextAccessible* text = aAccessible->AsHyperText();
+ if (text) {
+ matched = text->GetChildAtOffset(mStartOffset);
+ }
+ }
+
+ return matched;
+}
+
+Accessible*
+nsAccessiblePivot::SearchBackward(Accessible* aAccessible,
+ nsIAccessibleTraversalRule* aRule,
+ bool aSearchCurrent,
+ nsresult* aResult)
+{
+ *aResult = NS_OK;
+
+ // Initial position could be unset, in that case return null.
+ if (!aAccessible)
+ return nullptr;
+
+ RuleCache cache(aRule);
+ uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
+ Accessible* accessible = AdjustStartPosition(aAccessible, cache,
+ &filtered, aResult);
+ NS_ENSURE_SUCCESS(*aResult, nullptr);
+
+ if (aSearchCurrent && (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)) {
+ return accessible;
+ }
+
+ Accessible* root = GetActiveRoot();
+ while (accessible != root) {
+ Accessible* parent = accessible->Parent();
+ int32_t idxInParent = accessible->IndexInParent();
+ while (idxInParent > 0) {
+ if (!(accessible = parent->GetChildAt(--idxInParent)))
+ continue;
+
+ *aResult = cache.ApplyFilter(accessible, &filtered);
+ NS_ENSURE_SUCCESS(*aResult, nullptr);
+
+ Accessible* lastChild = nullptr;
+ while (!(filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) &&
+ (lastChild = accessible->LastChild())) {
+ parent = accessible;
+ accessible = lastChild;
+ idxInParent = accessible->IndexInParent();
+ *aResult = cache.ApplyFilter(accessible, &filtered);
+ NS_ENSURE_SUCCESS(*aResult, nullptr);
+ }
+
+ if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
+ return accessible;
+ }
+
+ if (!(accessible = parent))
+ break;
+
+ *aResult = cache.ApplyFilter(accessible, &filtered);
+ NS_ENSURE_SUCCESS(*aResult, nullptr);
+
+ if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
+ return accessible;
+ }
+
+ return nullptr;
+}
+
+Accessible*
+nsAccessiblePivot::SearchForward(Accessible* aAccessible,
+ nsIAccessibleTraversalRule* aRule,
+ bool aSearchCurrent,
+ nsresult* aResult)
+{
+ *aResult = NS_OK;
+
+ // Initial position could be not set, in that case begin search from root.
+ Accessible* root = GetActiveRoot();
+ Accessible* accessible = (!aAccessible) ? root : aAccessible;
+
+ RuleCache cache(aRule);
+
+ uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
+ accessible = AdjustStartPosition(accessible, cache, &filtered, aResult);
+ NS_ENSURE_SUCCESS(*aResult, nullptr);
+ if (aSearchCurrent && (filtered & nsIAccessibleTraversalRule::FILTER_MATCH))
+ return accessible;
+
+ while (true) {
+ Accessible* firstChild = nullptr;
+ while (!(filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) &&
+ (firstChild = accessible->FirstChild())) {
+ accessible = firstChild;
+ *aResult = cache.ApplyFilter(accessible, &filtered);
+ NS_ENSURE_SUCCESS(*aResult, nullptr);
+
+ if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
+ return accessible;
+ }
+
+ Accessible* sibling = nullptr;
+ Accessible* temp = accessible;
+ do {
+ if (temp == root)
+ break;
+
+ sibling = temp->NextSibling();
+
+ if (sibling)
+ break;
+ } while ((temp = temp->Parent()));
+
+ if (!sibling)
+ break;
+
+ accessible = sibling;
+ *aResult = cache.ApplyFilter(accessible, &filtered);
+ NS_ENSURE_SUCCESS(*aResult, nullptr);
+
+ if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
+ return accessible;
+ }
+
+ return nullptr;
+}
+
+HyperTextAccessible*
+nsAccessiblePivot::SearchForText(Accessible* aAccessible, bool aBackward)
+{
+ Accessible* root = GetActiveRoot();
+ Accessible* accessible = aAccessible;
+ while (true) {
+ Accessible* child = nullptr;
+
+ while ((child = (aBackward ? accessible->LastChild() :
+ accessible->FirstChild()))) {
+ accessible = child;
+ if (child->IsHyperText())
+ return child->AsHyperText();
+ }
+
+ Accessible* sibling = nullptr;
+ Accessible* temp = accessible;
+ do {
+ if (temp == root)
+ break;
+
+ if (temp != aAccessible && temp->IsHyperText())
+ return temp->AsHyperText();
+
+ sibling = aBackward ? temp->PrevSibling() : temp->NextSibling();
+
+ if (sibling)
+ break;
+ } while ((temp = temp->Parent()));
+
+ if (!sibling)
+ break;
+
+ accessible = sibling;
+ if (accessible->IsHyperText())
+ return accessible->AsHyperText();
+ }
+
+ return nullptr;
+}
+
+
+bool
+nsAccessiblePivot::NotifyOfPivotChange(Accessible* aOldPosition,
+ int32_t aOldStart, int32_t aOldEnd,
+ int16_t aReason, bool aIsFromUserInput)
+{
+ if (aOldPosition == mPosition &&
+ aOldStart == mStartOffset && aOldEnd == mEndOffset)
+ return false;
+
+ nsCOMPtr<nsIAccessible> xpcOldPos = ToXPC(aOldPosition); // death grip
+ nsTObserverArray<nsCOMPtr<nsIAccessiblePivotObserver> >::ForwardIterator iter(mObservers);
+ while (iter.HasMore()) {
+ nsIAccessiblePivotObserver* obs = iter.GetNext();
+ obs->OnPivotChanged(this, xpcOldPos, aOldStart, aOldEnd, aReason,
+ aIsFromUserInput);
+ }
+
+ return true;
+}
+
+nsresult
+RuleCache::ApplyFilter(Accessible* aAccessible, uint16_t* aResult)
+{
+ *aResult = nsIAccessibleTraversalRule::FILTER_IGNORE;
+
+ if (!mAcceptRoles) {
+ nsresult rv = mRule->GetMatchRoles(&mAcceptRoles, &mAcceptRolesLength);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mRule->GetPreFilter(&mPreFilter);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mPreFilter) {
+ uint64_t state = aAccessible->State();
+
+ if ((nsIAccessibleTraversalRule::PREFILTER_INVISIBLE & mPreFilter) &&
+ (state & states::INVISIBLE))
+ return NS_OK;
+
+ if ((nsIAccessibleTraversalRule::PREFILTER_OFFSCREEN & mPreFilter) &&
+ (state & states::OFFSCREEN))
+ return NS_OK;
+
+ if ((nsIAccessibleTraversalRule::PREFILTER_NOT_FOCUSABLE & mPreFilter) &&
+ !(state & states::FOCUSABLE))
+ return NS_OK;
+
+ if (nsIAccessibleTraversalRule::PREFILTER_ARIA_HIDDEN & mPreFilter) {
+ if (aAccessible->IsARIAHidden()) {
+ *aResult |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+ return NS_OK;
+ }
+ }
+
+ if ((nsIAccessibleTraversalRule::PREFILTER_TRANSPARENT & mPreFilter) &&
+ !(state & states::OPAQUE1)) {
+ nsIFrame* frame = aAccessible->GetFrame();
+ if (frame->StyleEffects()->mOpacity == 0.0f) {
+ *aResult |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+ return NS_OK;
+ }
+ }
+ }
+
+ if (mAcceptRolesLength > 0) {
+ uint32_t accessibleRole = aAccessible->Role();
+ bool matchesRole = false;
+ for (uint32_t idx = 0; idx < mAcceptRolesLength; idx++) {
+ matchesRole = mAcceptRoles[idx] == accessibleRole;
+ if (matchesRole)
+ break;
+ }
+ if (!matchesRole)
+ return NS_OK;
+ }
+
+ return mRule->Match(ToXPC(aAccessible), aResult);
+}
diff --git a/accessible/base/nsAccessiblePivot.h b/accessible/base/nsAccessiblePivot.h
new file mode 100644
index 000000000..a0e409aa5
--- /dev/null
+++ b/accessible/base/nsAccessiblePivot.h
@@ -0,0 +1,144 @@
+/* -*- 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 _nsAccessiblePivot_H_
+#define _nsAccessiblePivot_H_
+
+#include "nsIAccessiblePivot.h"
+
+#include "Accessible-inl.h"
+#include "nsTObserverArray.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Attributes.h"
+
+class RuleCache;
+
+/**
+ * Class represents an accessible pivot.
+ */
+class nsAccessiblePivot final : public nsIAccessiblePivot
+{
+public:
+ typedef mozilla::a11y::Accessible Accessible;
+
+ explicit nsAccessiblePivot(Accessible* aRoot);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsAccessiblePivot, nsIAccessiblePivot)
+
+ NS_DECL_NSIACCESSIBLEPIVOT
+
+ /*
+ * A simple getter for the pivot's position.
+ */
+ Accessible* Position() { return mPosition; }
+
+private:
+ ~nsAccessiblePivot();
+ nsAccessiblePivot() = delete;
+ nsAccessiblePivot(const nsAccessiblePivot&) = delete;
+ void operator = (const nsAccessiblePivot&) = delete;
+
+ /*
+ * Notify all observers on a pivot change. Return true if it has changed and
+ * observers have been notified.
+ */
+ bool NotifyOfPivotChange(Accessible* aOldAccessible,
+ int32_t aOldStart, int32_t aOldEnd,
+ PivotMoveReason aReason,
+ bool aIsFromUserInput);
+
+ /*
+ * Check to see that the given accessible is a descendant of given ancestor
+ */
+ bool IsDescendantOf(Accessible* aAccessible, Accessible* aAncestor);
+
+
+ /*
+ * Search in preorder for the first accessible to match the rule.
+ */
+ Accessible* SearchForward(Accessible* aAccessible,
+ nsIAccessibleTraversalRule* aRule,
+ bool aSearchCurrent,
+ nsresult* aResult);
+
+ /*
+ * Reverse search in preorder for the first accessible to match the rule.
+ */
+ Accessible* SearchBackward(Accessible* aAccessible,
+ nsIAccessibleTraversalRule* aRule,
+ bool aSearchCurrent,
+ nsresult* aResult);
+
+ /*
+ * Search in preorder for the first text accessible.
+ */
+ mozilla::a11y::HyperTextAccessible* SearchForText(Accessible* aAccessible,
+ bool aBackward);
+
+ /*
+ * Get the effective root for this pivot, either the true root or modal root.
+ */
+ Accessible* GetActiveRoot() const
+ {
+ if (mModalRoot) {
+ NS_ENSURE_FALSE(mModalRoot->IsDefunct(), mRoot);
+ return mModalRoot;
+ }
+
+ return mRoot;
+ }
+
+ /*
+ * Update the pivot, and notify observers. Return true if it moved.
+ */
+ bool MovePivotInternal(Accessible* aPosition, PivotMoveReason aReason,
+ bool aIsFromUserInput);
+
+ /*
+ * Get initial node we should start a search from with a given rule.
+ *
+ * When we do a move operation from one position to another,
+ * the initial position can be inside of a subtree that is ignored by
+ * the given rule. We need to step out of the ignored subtree and start
+ * the search from there.
+ *
+ */
+ Accessible* AdjustStartPosition(Accessible* aAccessible, RuleCache& aCache,
+ uint16_t* aFilterResult, nsresult* aResult);
+
+ /*
+ * The root accessible.
+ */
+ RefPtr<Accessible> mRoot;
+
+ /*
+ * The temporary modal root accessible.
+ */
+ RefPtr<Accessible> mModalRoot;
+
+ /*
+ * The current pivot position.
+ */
+ RefPtr<Accessible> mPosition;
+
+ /*
+ * The text start offset ofthe pivot.
+ */
+ int32_t mStartOffset;
+
+ /*
+ * The text end offset ofthe pivot.
+ */
+ int32_t mEndOffset;
+
+ /*
+ * The list of pivot-changed observers.
+ */
+ nsTObserverArray<nsCOMPtr<nsIAccessiblePivotObserver> > mObservers;
+};
+
+#endif
diff --git a/accessible/base/nsCoreUtils.cpp b/accessible/base/nsCoreUtils.cpp
new file mode 100644
index 000000000..89823b223
--- /dev/null
+++ b/accessible/base/nsCoreUtils.cpp
@@ -0,0 +1,683 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsCoreUtils.h"
+
+#include "nsIAccessibleTypes.h"
+
+#include "nsIBaseWindow.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIDocument.h"
+#include "nsIDOMHTMLDocument.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsRange.h"
+#include "nsIBoxObject.h"
+#include "nsIDOMXULElement.h"
+#include "nsIDocShell.h"
+#include "nsIObserverService.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "nsIScrollableFrame.h"
+#include "nsISelectionPrivate.h"
+#include "nsISelectionController.h"
+#include "nsISimpleEnumerator.h"
+#include "mozilla/dom/TouchEvent.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TouchEvents.h"
+#include "nsView.h"
+#include "nsGkAtoms.h"
+
+#include "nsComponentManagerUtils.h"
+
+#include "nsITreeBoxObject.h"
+#include "nsITreeColumns.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLLabelElement.h"
+
+using namespace mozilla;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsCoreUtils
+////////////////////////////////////////////////////////////////////////////////
+
+bool
+nsCoreUtils::IsLabelWithControl(nsIContent* aContent)
+{
+ dom::HTMLLabelElement* label = dom::HTMLLabelElement::FromContent(aContent);
+ if (label && label->GetControl())
+ return true;
+
+ return false;
+}
+
+bool
+nsCoreUtils::HasClickListener(nsIContent *aContent)
+{
+ NS_ENSURE_TRUE(aContent, false);
+ EventListenerManager* listenerManager =
+ aContent->GetExistingListenerManager();
+
+ return listenerManager &&
+ (listenerManager->HasListenersFor(nsGkAtoms::onclick) ||
+ listenerManager->HasListenersFor(nsGkAtoms::onmousedown) ||
+ listenerManager->HasListenersFor(nsGkAtoms::onmouseup));
+}
+
+void
+nsCoreUtils::DispatchClickEvent(nsITreeBoxObject *aTreeBoxObj,
+ int32_t aRowIndex, nsITreeColumn *aColumn,
+ const nsAString& aPseudoElt)
+{
+ nsCOMPtr<nsIDOMElement> tcElm;
+ aTreeBoxObj->GetTreeBody(getter_AddRefs(tcElm));
+ if (!tcElm)
+ return;
+
+ nsCOMPtr<nsIContent> tcContent(do_QueryInterface(tcElm));
+ nsIDocument *document = tcContent->GetUncomposedDoc();
+ if (!document)
+ return;
+
+ nsCOMPtr<nsIPresShell> presShell = document->GetShell();
+ if (!presShell)
+ return;
+
+ // Ensure row is visible.
+ aTreeBoxObj->EnsureRowIsVisible(aRowIndex);
+
+ // Calculate x and y coordinates.
+ int32_t x = 0, y = 0, width = 0, height = 0;
+ nsresult rv = aTreeBoxObj->GetCoordsForCellItem(aRowIndex, aColumn,
+ aPseudoElt,
+ &x, &y, &width, &height);
+ if (NS_FAILED(rv))
+ return;
+
+ nsCOMPtr<nsIDOMXULElement> tcXULElm(do_QueryInterface(tcElm));
+ nsCOMPtr<nsIBoxObject> tcBoxObj;
+ tcXULElm->GetBoxObject(getter_AddRefs(tcBoxObj));
+
+ int32_t tcX = 0;
+ tcBoxObj->GetX(&tcX);
+
+ int32_t tcY = 0;
+ tcBoxObj->GetY(&tcY);
+
+ // Dispatch mouse events.
+ nsWeakFrame tcFrame = tcContent->GetPrimaryFrame();
+ nsIFrame* rootFrame = presShell->GetRootFrame();
+
+ nsPoint offset;
+ nsIWidget *rootWidget =
+ rootFrame->GetView()->GetNearestWidget(&offset);
+
+ RefPtr<nsPresContext> presContext = presShell->GetPresContext();
+
+ int32_t cnvdX = presContext->CSSPixelsToDevPixels(tcX + x + 1) +
+ presContext->AppUnitsToDevPixels(offset.x);
+ int32_t cnvdY = presContext->CSSPixelsToDevPixels(tcY + y + 1) +
+ presContext->AppUnitsToDevPixels(offset.y);
+
+ // XUL is just desktop, so there is no real reason for senfing touch events.
+ DispatchMouseEvent(eMouseDown, cnvdX, cnvdY,
+ tcContent, tcFrame, presShell, rootWidget);
+
+ DispatchMouseEvent(eMouseUp, cnvdX, cnvdY,
+ tcContent, tcFrame, presShell, rootWidget);
+}
+
+void
+nsCoreUtils::DispatchMouseEvent(EventMessage aMessage, int32_t aX, int32_t aY,
+ nsIContent *aContent, nsIFrame *aFrame,
+ nsIPresShell *aPresShell, nsIWidget *aRootWidget)
+{
+ WidgetMouseEvent event(true, aMessage, aRootWidget,
+ WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
+
+ event.mRefPoint = LayoutDeviceIntPoint(aX, aY);
+
+ event.mClickCount = 1;
+ event.button = WidgetMouseEvent::eLeftButton;
+ event.mTime = PR_IntervalNow();
+ event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ aPresShell->HandleEventWithTarget(&event, aFrame, aContent, &status);
+}
+
+void
+nsCoreUtils::DispatchTouchEvent(EventMessage aMessage, int32_t aX, int32_t aY,
+ nsIContent* aContent, nsIFrame* aFrame,
+ nsIPresShell* aPresShell, nsIWidget* aRootWidget)
+{
+ nsIDocShell* docShell = nullptr;
+ if (aPresShell->GetDocument()) {
+ docShell = aPresShell->GetDocument()->GetDocShell();
+ }
+ if (!dom::TouchEvent::PrefEnabled(docShell)) {
+ return;
+ }
+
+ WidgetTouchEvent event(true, aMessage, aRootWidget);
+
+ event.mTime = PR_IntervalNow();
+
+ // XXX: Touch has an identifier of -1 to hint that it is synthesized.
+ RefPtr<dom::Touch> t = new dom::Touch(-1, LayoutDeviceIntPoint(aX, aY),
+ LayoutDeviceIntPoint(1, 1), 0.0f, 1.0f);
+ t->SetTarget(aContent);
+ event.mTouches.AppendElement(t);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ aPresShell->HandleEventWithTarget(&event, aFrame, aContent, &status);
+}
+
+uint32_t
+nsCoreUtils::GetAccessKeyFor(nsIContent* aContent)
+{
+ // Accesskeys are registered by @accesskey attribute only. At first check
+ // whether it is presented on the given element to avoid the slow
+ // EventStateManager::GetRegisteredAccessKey() method.
+ if (!aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::accesskey))
+ return 0;
+
+ nsIPresShell* presShell = aContent->OwnerDoc()->GetShell();
+ if (!presShell)
+ return 0;
+
+ nsPresContext *presContext = presShell->GetPresContext();
+ if (!presContext)
+ return 0;
+
+ EventStateManager *esm = presContext->EventStateManager();
+ if (!esm)
+ return 0;
+
+ return esm->GetRegisteredAccessKey(aContent);
+}
+
+nsIContent *
+nsCoreUtils::GetDOMElementFor(nsIContent *aContent)
+{
+ if (aContent->IsElement())
+ return aContent;
+
+ if (aContent->IsNodeOfType(nsINode::eTEXT))
+ return aContent->GetFlattenedTreeParent();
+
+ return nullptr;
+}
+
+nsINode *
+nsCoreUtils::GetDOMNodeFromDOMPoint(nsINode *aNode, uint32_t aOffset)
+{
+ if (aNode && aNode->IsElement()) {
+ uint32_t childCount = aNode->GetChildCount();
+ NS_ASSERTION(aOffset <= childCount, "Wrong offset of the DOM point!");
+
+ // The offset can be after last child of container node that means DOM point
+ // is placed immediately after the last child. In this case use the DOM node
+ // from the given DOM point is used as result node.
+ if (aOffset != childCount)
+ return aNode->GetChildAt(aOffset);
+ }
+
+ return aNode;
+}
+
+bool
+nsCoreUtils::IsAncestorOf(nsINode *aPossibleAncestorNode,
+ nsINode *aPossibleDescendantNode,
+ nsINode *aRootNode)
+{
+ NS_ENSURE_TRUE(aPossibleAncestorNode && aPossibleDescendantNode, false);
+
+ nsINode *parentNode = aPossibleDescendantNode;
+ while ((parentNode = parentNode->GetParentNode()) &&
+ parentNode != aRootNode) {
+ if (parentNode == aPossibleAncestorNode)
+ return true;
+ }
+
+ return false;
+}
+
+nsresult
+nsCoreUtils::ScrollSubstringTo(nsIFrame* aFrame, nsRange* aRange,
+ uint32_t aScrollType)
+{
+ nsIPresShell::ScrollAxis vertical, horizontal;
+ ConvertScrollTypeToPercents(aScrollType, &vertical, &horizontal);
+
+ return ScrollSubstringTo(aFrame, aRange, vertical, horizontal);
+}
+
+nsresult
+nsCoreUtils::ScrollSubstringTo(nsIFrame* aFrame, nsRange* aRange,
+ nsIPresShell::ScrollAxis aVertical,
+ nsIPresShell::ScrollAxis aHorizontal)
+{
+ if (!aFrame)
+ return NS_ERROR_FAILURE;
+
+ nsPresContext *presContext = aFrame->PresContext();
+
+ nsCOMPtr<nsISelectionController> selCon;
+ aFrame->GetSelectionController(presContext, getter_AddRefs(selCon));
+ NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsISelection> selection;
+ selCon->GetSelection(nsISelectionController::SELECTION_ACCESSIBILITY,
+ getter_AddRefs(selection));
+
+ nsCOMPtr<nsISelectionPrivate> privSel(do_QueryInterface(selection));
+ selection->RemoveAllRanges();
+ selection->AddRange(aRange);
+
+ privSel->ScrollIntoViewInternal(
+ nsISelectionController::SELECTION_ANCHOR_REGION,
+ true, aVertical, aHorizontal);
+
+ selection->CollapseToStart();
+
+ return NS_OK;
+}
+
+void
+nsCoreUtils::ScrollFrameToPoint(nsIFrame *aScrollableFrame,
+ nsIFrame *aFrame,
+ const nsIntPoint& aPoint)
+{
+ nsIScrollableFrame* scrollableFrame = do_QueryFrame(aScrollableFrame);
+ if (!scrollableFrame)
+ return;
+
+ nsPoint point =
+ ToAppUnits(aPoint, aFrame->PresContext()->AppUnitsPerDevPixel());
+ nsRect frameRect = aFrame->GetScreenRectInAppUnits();
+ nsPoint deltaPoint(point.x - frameRect.x, point.y - frameRect.y);
+
+ nsPoint scrollPoint = scrollableFrame->GetScrollPosition();
+ scrollPoint -= deltaPoint;
+
+ scrollableFrame->ScrollTo(scrollPoint, nsIScrollableFrame::INSTANT);
+}
+
+void
+nsCoreUtils::ConvertScrollTypeToPercents(uint32_t aScrollType,
+ nsIPresShell::ScrollAxis *aVertical,
+ nsIPresShell::ScrollAxis *aHorizontal)
+{
+ int16_t whereY, whereX;
+ nsIPresShell::WhenToScroll whenY, whenX;
+ switch (aScrollType)
+ {
+ case nsIAccessibleScrollType::SCROLL_TYPE_TOP_LEFT:
+ whereY = nsIPresShell::SCROLL_TOP;
+ whenY = nsIPresShell::SCROLL_ALWAYS;
+ whereX = nsIPresShell::SCROLL_LEFT;
+ whenX = nsIPresShell::SCROLL_ALWAYS;
+ break;
+ case nsIAccessibleScrollType::SCROLL_TYPE_BOTTOM_RIGHT:
+ whereY = nsIPresShell::SCROLL_BOTTOM;
+ whenY = nsIPresShell::SCROLL_ALWAYS;
+ whereX = nsIPresShell::SCROLL_RIGHT;
+ whenX = nsIPresShell::SCROLL_ALWAYS;
+ break;
+ case nsIAccessibleScrollType::SCROLL_TYPE_TOP_EDGE:
+ whereY = nsIPresShell::SCROLL_TOP;
+ whenY = nsIPresShell::SCROLL_ALWAYS;
+ whereX = nsIPresShell::SCROLL_MINIMUM;
+ whenX = nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE;
+ break;
+ case nsIAccessibleScrollType::SCROLL_TYPE_BOTTOM_EDGE:
+ whereY = nsIPresShell::SCROLL_BOTTOM;
+ whenY = nsIPresShell::SCROLL_ALWAYS;
+ whereX = nsIPresShell::SCROLL_MINIMUM;
+ whenX = nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE;
+ break;
+ case nsIAccessibleScrollType::SCROLL_TYPE_LEFT_EDGE:
+ whereY = nsIPresShell::SCROLL_MINIMUM;
+ whenY = nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE;
+ whereX = nsIPresShell::SCROLL_LEFT;
+ whenX = nsIPresShell::SCROLL_ALWAYS;
+ break;
+ case nsIAccessibleScrollType::SCROLL_TYPE_RIGHT_EDGE:
+ whereY = nsIPresShell::SCROLL_MINIMUM;
+ whenY = nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE;
+ whereX = nsIPresShell::SCROLL_RIGHT;
+ whenX = nsIPresShell::SCROLL_ALWAYS;
+ break;
+ default:
+ whereY = nsIPresShell::SCROLL_MINIMUM;
+ whenY = nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE;
+ whereX = nsIPresShell::SCROLL_MINIMUM;
+ whenX = nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE;
+ }
+ *aVertical = nsIPresShell::ScrollAxis(whereY, whenY);
+ *aHorizontal = nsIPresShell::ScrollAxis(whereX, whenX);
+}
+
+nsIntPoint
+nsCoreUtils::GetScreenCoordsForWindow(nsINode *aNode)
+{
+ nsIntPoint coords(0, 0);
+ nsCOMPtr<nsIDocShellTreeItem> treeItem(GetDocShellFor(aNode));
+ if (!treeItem)
+ return coords;
+
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+ treeItem->GetTreeOwner(getter_AddRefs(treeOwner));
+ if (!treeOwner)
+ return coords;
+
+ nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner);
+ if (baseWindow)
+ baseWindow->GetPosition(&coords.x, &coords.y); // in device pixels
+
+ return coords;
+}
+
+already_AddRefed<nsIDocShell>
+nsCoreUtils::GetDocShellFor(nsINode *aNode)
+{
+ if (!aNode)
+ return nullptr;
+
+ nsCOMPtr<nsIDocShell> docShell = aNode->OwnerDoc()->GetDocShell();
+ return docShell.forget();
+}
+
+bool
+nsCoreUtils::IsRootDocument(nsIDocument *aDocument)
+{
+ nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = aDocument->GetDocShell();
+ NS_ASSERTION(docShellTreeItem, "No document shell for document!");
+
+ nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
+ docShellTreeItem->GetParent(getter_AddRefs(parentTreeItem));
+
+ return !parentTreeItem;
+}
+
+bool
+nsCoreUtils::IsContentDocument(nsIDocument *aDocument)
+{
+ nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = aDocument->GetDocShell();
+ NS_ASSERTION(docShellTreeItem, "No document shell tree item for document!");
+
+ return (docShellTreeItem->ItemType() == nsIDocShellTreeItem::typeContent);
+}
+
+bool
+nsCoreUtils::IsTabDocument(nsIDocument* aDocumentNode)
+{
+ nsCOMPtr<nsIDocShellTreeItem> treeItem(aDocumentNode->GetDocShell());
+
+ nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
+ treeItem->GetParent(getter_AddRefs(parentTreeItem));
+
+ // Tab document running in own process doesn't have parent.
+ if (XRE_IsContentProcess())
+ return !parentTreeItem;
+
+ // Parent of docshell for tab document running in chrome process is root.
+ nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
+ treeItem->GetRootTreeItem(getter_AddRefs(rootTreeItem));
+
+ return parentTreeItem == rootTreeItem;
+}
+
+bool
+nsCoreUtils::IsErrorPage(nsIDocument *aDocument)
+{
+ nsIURI *uri = aDocument->GetDocumentURI();
+ bool isAboutScheme = false;
+ uri->SchemeIs("about", &isAboutScheme);
+ if (!isAboutScheme)
+ return false;
+
+ nsAutoCString path;
+ uri->GetPath(path);
+
+ NS_NAMED_LITERAL_CSTRING(neterror, "neterror");
+ NS_NAMED_LITERAL_CSTRING(certerror, "certerror");
+
+ return StringBeginsWith(path, neterror) || StringBeginsWith(path, certerror);
+}
+
+bool
+nsCoreUtils::GetID(nsIContent *aContent, nsAString& aID)
+{
+ return aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::id, aID);
+}
+
+bool
+nsCoreUtils::GetUIntAttr(nsIContent *aContent, nsIAtom *aAttr, int32_t *aUInt)
+{
+ nsAutoString value;
+ aContent->GetAttr(kNameSpaceID_None, aAttr, value);
+ if (!value.IsEmpty()) {
+ nsresult error = NS_OK;
+ int32_t integer = value.ToInteger(&error);
+ if (NS_SUCCEEDED(error) && integer > 0) {
+ *aUInt = integer;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+nsCoreUtils::GetLanguageFor(nsIContent *aContent, nsIContent *aRootContent,
+ nsAString& aLanguage)
+{
+ aLanguage.Truncate();
+
+ nsIContent *walkUp = aContent;
+ while (walkUp && walkUp != aRootContent &&
+ !walkUp->GetAttr(kNameSpaceID_None, nsGkAtoms::lang, aLanguage))
+ walkUp = walkUp->GetParent();
+}
+
+already_AddRefed<nsIBoxObject>
+nsCoreUtils::GetTreeBodyBoxObject(nsITreeBoxObject *aTreeBoxObj)
+{
+ nsCOMPtr<nsIDOMElement> tcElm;
+ aTreeBoxObj->GetTreeBody(getter_AddRefs(tcElm));
+ nsCOMPtr<nsIDOMXULElement> tcXULElm(do_QueryInterface(tcElm));
+ if (!tcXULElm)
+ return nullptr;
+
+ nsCOMPtr<nsIBoxObject> boxObj;
+ tcXULElm->GetBoxObject(getter_AddRefs(boxObj));
+ return boxObj.forget();
+}
+
+already_AddRefed<nsITreeBoxObject>
+nsCoreUtils::GetTreeBoxObject(nsIContent *aContent)
+{
+ // Find DOMNode's parents recursively until reach the <tree> tag
+ nsIContent* currentContent = aContent;
+ while (currentContent) {
+ if (currentContent->NodeInfo()->Equals(nsGkAtoms::tree,
+ kNameSpaceID_XUL)) {
+ // We will get the nsITreeBoxObject from the tree node
+ nsCOMPtr<nsIDOMXULElement> xulElement(do_QueryInterface(currentContent));
+ if (xulElement) {
+ nsCOMPtr<nsIBoxObject> box;
+ xulElement->GetBoxObject(getter_AddRefs(box));
+ nsCOMPtr<nsITreeBoxObject> treeBox(do_QueryInterface(box));
+ if (treeBox)
+ return treeBox.forget();
+ }
+ }
+ currentContent = currentContent->GetFlattenedTreeParent();
+ }
+
+ return nullptr;
+}
+
+already_AddRefed<nsITreeColumn>
+nsCoreUtils::GetFirstSensibleColumn(nsITreeBoxObject *aTree)
+{
+ nsCOMPtr<nsITreeColumns> cols;
+ aTree->GetColumns(getter_AddRefs(cols));
+ if (!cols)
+ return nullptr;
+
+ nsCOMPtr<nsITreeColumn> column;
+ cols->GetFirstColumn(getter_AddRefs(column));
+ if (column && IsColumnHidden(column))
+ return GetNextSensibleColumn(column);
+
+ return column.forget();
+}
+
+uint32_t
+nsCoreUtils::GetSensibleColumnCount(nsITreeBoxObject *aTree)
+{
+ uint32_t count = 0;
+
+ nsCOMPtr<nsITreeColumns> cols;
+ aTree->GetColumns(getter_AddRefs(cols));
+ if (!cols)
+ return count;
+
+ nsCOMPtr<nsITreeColumn> column;
+ cols->GetFirstColumn(getter_AddRefs(column));
+
+ while (column) {
+ if (!IsColumnHidden(column))
+ count++;
+
+ nsCOMPtr<nsITreeColumn> nextColumn;
+ column->GetNext(getter_AddRefs(nextColumn));
+ column.swap(nextColumn);
+ }
+
+ return count;
+}
+
+already_AddRefed<nsITreeColumn>
+nsCoreUtils::GetSensibleColumnAt(nsITreeBoxObject *aTree, uint32_t aIndex)
+{
+ uint32_t idx = aIndex;
+
+ nsCOMPtr<nsITreeColumn> column = GetFirstSensibleColumn(aTree);
+ while (column) {
+ if (idx == 0)
+ return column.forget();
+
+ idx--;
+ column = GetNextSensibleColumn(column);
+ }
+
+ return nullptr;
+}
+
+already_AddRefed<nsITreeColumn>
+nsCoreUtils::GetNextSensibleColumn(nsITreeColumn *aColumn)
+{
+ nsCOMPtr<nsITreeColumn> nextColumn;
+ aColumn->GetNext(getter_AddRefs(nextColumn));
+
+ while (nextColumn && IsColumnHidden(nextColumn)) {
+ nsCOMPtr<nsITreeColumn> tempColumn;
+ nextColumn->GetNext(getter_AddRefs(tempColumn));
+ nextColumn.swap(tempColumn);
+ }
+
+ return nextColumn.forget();
+}
+
+already_AddRefed<nsITreeColumn>
+nsCoreUtils::GetPreviousSensibleColumn(nsITreeColumn *aColumn)
+{
+ nsCOMPtr<nsITreeColumn> prevColumn;
+ aColumn->GetPrevious(getter_AddRefs(prevColumn));
+
+ while (prevColumn && IsColumnHidden(prevColumn)) {
+ nsCOMPtr<nsITreeColumn> tempColumn;
+ prevColumn->GetPrevious(getter_AddRefs(tempColumn));
+ prevColumn.swap(tempColumn);
+ }
+
+ return prevColumn.forget();
+}
+
+bool
+nsCoreUtils::IsColumnHidden(nsITreeColumn *aColumn)
+{
+ nsCOMPtr<nsIDOMElement> element;
+ aColumn->GetElement(getter_AddRefs(element));
+ nsCOMPtr<nsIContent> content = do_QueryInterface(element);
+ return content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
+ nsGkAtoms::_true, eCaseMatters);
+}
+
+void
+nsCoreUtils::ScrollTo(nsIPresShell* aPresShell, nsIContent* aContent,
+ uint32_t aScrollType)
+{
+ nsIPresShell::ScrollAxis vertical, horizontal;
+ ConvertScrollTypeToPercents(aScrollType, &vertical, &horizontal);
+ aPresShell->ScrollContentIntoView(aContent, vertical, horizontal,
+ nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
+}
+
+bool
+nsCoreUtils::IsWhitespaceString(const nsSubstring& aString)
+{
+ nsSubstring::const_char_iterator iterBegin, iterEnd;
+
+ aString.BeginReading(iterBegin);
+ aString.EndReading(iterEnd);
+
+ while (iterBegin != iterEnd && IsWhitespace(*iterBegin))
+ ++iterBegin;
+
+ return iterBegin == iterEnd;
+}
+
+bool
+nsCoreUtils::AccEventObserversExist()
+{
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ NS_ENSURE_TRUE(obsService, false);
+
+ nsCOMPtr<nsISimpleEnumerator> observers;
+ obsService->EnumerateObservers(NS_ACCESSIBLE_EVENT_TOPIC,
+ getter_AddRefs(observers));
+ NS_ENSURE_TRUE(observers, false);
+
+ bool hasObservers = false;
+ observers->HasMoreElements(&hasObservers);
+
+ return hasObservers;
+}
+
+void
+nsCoreUtils::DispatchAccEvent(RefPtr<nsIAccessibleEvent> event)
+{
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ NS_ENSURE_TRUE_VOID(obsService);
+
+ obsService->NotifyObservers(event, NS_ACCESSIBLE_EVENT_TOPIC, nullptr);
+}
+
+void
+nsCoreUtils::XBLBindingRole(const nsIContent* aEl, nsAString& aRole)
+{
+ for (const nsXBLBinding* binding = aEl->GetXBLBinding(); binding;
+ binding = binding->GetBaseBinding()) {
+ nsIContent* bindingElm = binding->PrototypeBinding()->GetBindingElement();
+ bindingElm->GetAttr(kNameSpaceID_None, nsGkAtoms::role, aRole);
+ if (!aRole.IsEmpty())
+ break;
+ }
+}
diff --git a/accessible/base/nsCoreUtils.h b/accessible/base/nsCoreUtils.h
new file mode 100644
index 000000000..8d561e55b
--- /dev/null
+++ b/accessible/base/nsCoreUtils.h
@@ -0,0 +1,329 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 nsCoreUtils_h_
+#define nsCoreUtils_h_
+
+#include "mozilla/EventForwards.h"
+#include "nsIAccessibleEvent.h"
+#include "nsIContent.h"
+#include "nsIDocument.h" // for GetShell()
+#include "nsIPresShell.h"
+
+#include "nsPoint.h"
+#include "nsTArray.h"
+
+class nsRange;
+class nsIBoxObject;
+class nsIFrame;
+class nsIDocShell;
+class nsITreeColumn;
+class nsITreeBoxObject;
+class nsIWidget;
+
+/**
+ * Core utils.
+ */
+class nsCoreUtils
+{
+public:
+ /**
+ * Return true if the given node is a label of a control.
+ */
+ static bool IsLabelWithControl(nsIContent *aContent);
+
+ /**
+ * Return true if the given node has registered click, mousedown or mouseup
+ * event listeners.
+ */
+ static bool HasClickListener(nsIContent *aContent);
+
+ /**
+ * Dispatch click event to XUL tree cell.
+ *
+ * @param aTreeBoxObj [in] tree box object
+ * @param aRowIndex [in] row index
+ * @param aColumn [in] column object
+ * @param aPseudoElm [in] pseudo elemenet inside the cell, see
+ * nsITreeBoxObject for available values
+ */
+ static void DispatchClickEvent(nsITreeBoxObject *aTreeBoxObj,
+ int32_t aRowIndex, nsITreeColumn *aColumn,
+ const nsAString& aPseudoElt = EmptyString());
+
+ /**
+ * Send mouse event to the given element.
+ *
+ * @param aMessage [in] an event message (see EventForwards.h)
+ * @param aX [in] x coordinate in dev pixels
+ * @param aY [in] y coordinate in dev pixels
+ * @param aContent [in] the element
+ * @param aFrame [in] frame of the element
+ * @param aPresShell [in] the presshell for the element
+ * @param aRootWidget [in] the root widget of the element
+ */
+ static void DispatchMouseEvent(mozilla::EventMessage aMessage,
+ int32_t aX, int32_t aY,
+ nsIContent *aContent, nsIFrame *aFrame,
+ nsIPresShell *aPresShell, nsIWidget *aRootWidget);
+
+ /**
+ * Send a touch event with a single touch point to the given element.
+ *
+ * @param aMessage [in] an event message (see EventForwards.h)
+ * @param aX [in] x coordinate in dev pixels
+ * @param aY [in] y coordinate in dev pixels
+ * @param aContent [in] the element
+ * @param aFrame [in] frame of the element
+ * @param aPresShell [in] the presshell for the element
+ * @param aRootWidget [in] the root widget of the element
+ */
+ static void DispatchTouchEvent(mozilla::EventMessage aMessage,
+ int32_t aX, int32_t aY,
+ nsIContent* aContent, nsIFrame* aFrame,
+ nsIPresShell* aPresShell, nsIWidget* aRootWidget);
+
+ /**
+ * Return an accesskey registered on the given element by
+ * EventStateManager or 0 if there is no registered accesskey.
+ *
+ * @param aContent - the given element.
+ */
+ static uint32_t GetAccessKeyFor(nsIContent *aContent);
+
+ /**
+ * Return DOM element related with the given node, i.e.
+ * a) itself if it is DOM element
+ * b) parent element if it is text node
+ * c) otherwise nullptr
+ *
+ * @param aNode [in] the given DOM node
+ */
+ static nsIContent* GetDOMElementFor(nsIContent *aContent);
+
+ /**
+ * Return DOM node for the given DOM point.
+ */
+ static nsINode *GetDOMNodeFromDOMPoint(nsINode *aNode, uint32_t aOffset);
+
+ /**
+ * Is the first passed in node an ancestor of the second?
+ * Note: A node is not considered to be the ancestor of itself.
+ *
+ * @param aPossibleAncestorNode [in] node to test for ancestor-ness of
+ * aPossibleDescendantNode
+ * @param aPossibleDescendantNode [in] node to test for descendant-ness of
+ * aPossibleAncestorNode
+ * @param aRootNode [in, optional] the root node that search
+ * search should be performed within
+ * @return true if aPossibleAncestorNode is an ancestor of
+ * aPossibleDescendantNode
+ */
+ static bool IsAncestorOf(nsINode *aPossibleAncestorNode,
+ nsINode *aPossibleDescendantNode,
+ nsINode *aRootNode = nullptr);
+
+ /**
+ * Helper method to scroll range into view, used for implementation of
+ * nsIAccessibleText::scrollSubstringTo().
+ *
+ * @param aFrame the frame for accessible the range belongs to.
+ * @param aRange the range to scroll to
+ * @param aScrollType the place a range should be scrolled to
+ */
+ static nsresult ScrollSubstringTo(nsIFrame* aFrame, nsRange* aRange,
+ uint32_t aScrollType);
+
+ /** Helper method to scroll range into view, used for implementation of
+ * nsIAccessibleText::scrollSubstringTo[Point]().
+ *
+ * @param aFrame the frame for accessible the range belongs to.
+ * @param aRange the range to scroll to
+ * @param aVertical how to align vertically, specified in percents, and when.
+ * @param aHorizontal how to align horizontally, specified in percents, and when.
+ */
+ static nsresult ScrollSubstringTo(nsIFrame* aFrame, nsRange* aRange,
+ nsIPresShell::ScrollAxis aVertical,
+ nsIPresShell::ScrollAxis aHorizontal);
+
+ /**
+ * Scrolls the given frame to the point, used for implememntation of
+ * nsIAccessible::scrollToPoint and nsIAccessibleText::scrollSubstringToPoint.
+ *
+ * @param aScrollableFrame the scrollable frame
+ * @param aFrame the frame to scroll
+ * @param aPoint the point scroll to
+ */
+ static void ScrollFrameToPoint(nsIFrame *aScrollableFrame,
+ nsIFrame *aFrame, const nsIntPoint& aPoint);
+
+ /**
+ * Converts scroll type constant defined in nsIAccessibleScrollType to
+ * vertical and horizontal parameters.
+ */
+ static void ConvertScrollTypeToPercents(uint32_t aScrollType,
+ nsIPresShell::ScrollAxis *aVertical,
+ nsIPresShell::ScrollAxis *aHorizontal);
+
+ /**
+ * Returns coordinates in device pixels relative screen for the top level
+ * window.
+ *
+ * @param aNode the DOM node hosted in the window.
+ */
+ static nsIntPoint GetScreenCoordsForWindow(nsINode *aNode);
+
+ /**
+ * Return document shell for the given DOM node.
+ */
+ static already_AddRefed<nsIDocShell> GetDocShellFor(nsINode *aNode);
+
+ /**
+ * Return true if the given document is root document.
+ */
+ static bool IsRootDocument(nsIDocument *aDocument);
+
+ /**
+ * Return true if the given document is content document (not chrome).
+ */
+ static bool IsContentDocument(nsIDocument *aDocument);
+
+ /**
+ * Return true if the given document node is for tab document accessible.
+ */
+ static bool IsTabDocument(nsIDocument* aDocumentNode);
+
+ /**
+ * Return true if the given document is an error page.
+ */
+ static bool IsErrorPage(nsIDocument *aDocument);
+
+ /**
+ * Return presShell for the document containing the given DOM node.
+ */
+ static nsIPresShell *GetPresShellFor(nsINode *aNode)
+ {
+ return aNode->OwnerDoc()->GetShell();
+ }
+
+ /**
+ * Get the ID for an element, in some types of XML this may not be the ID attribute
+ * @param aContent Node to get the ID for
+ * @param aID Where to put ID string
+ * @return true if there is an ID set for this node
+ */
+ static bool GetID(nsIContent *aContent, nsAString& aID);
+
+ /**
+ * Convert attribute value of the given node to positive integer. If no
+ * attribute or wrong value then false is returned.
+ */
+ static bool GetUIntAttr(nsIContent *aContent, nsIAtom *aAttr,
+ int32_t* aUInt);
+
+ /**
+ * Returns language for the given node.
+ *
+ * @param aContent [in] the given node
+ * @param aRootContent [in] container of the given node
+ * @param aLanguage [out] language
+ */
+ static void GetLanguageFor(nsIContent *aContent, nsIContent *aRootContent,
+ nsAString& aLanguage);
+
+ /**
+ * Return box object for XUL treechildren element by tree box object.
+ */
+ static already_AddRefed<nsIBoxObject>
+ GetTreeBodyBoxObject(nsITreeBoxObject *aTreeBoxObj);
+
+ /**
+ * Return tree box object from any levels DOMNode under the XUL tree.
+ */
+ static already_AddRefed<nsITreeBoxObject>
+ GetTreeBoxObject(nsIContent* aContent);
+
+ /**
+ * Return first sensible column for the given tree box object.
+ */
+ static already_AddRefed<nsITreeColumn>
+ GetFirstSensibleColumn(nsITreeBoxObject *aTree);
+
+ /**
+ * Return sensible columns count for the given tree box object.
+ */
+ static uint32_t GetSensibleColumnCount(nsITreeBoxObject *aTree);
+
+ /**
+ * Return sensible column at the given index for the given tree box object.
+ */
+ static already_AddRefed<nsITreeColumn>
+ GetSensibleColumnAt(nsITreeBoxObject *aTree, uint32_t aIndex);
+
+ /**
+ * Return next sensible column for the given column.
+ */
+ static already_AddRefed<nsITreeColumn>
+ GetNextSensibleColumn(nsITreeColumn *aColumn);
+
+ /**
+ * Return previous sensible column for the given column.
+ */
+ static already_AddRefed<nsITreeColumn>
+ GetPreviousSensibleColumn(nsITreeColumn *aColumn);
+
+ /**
+ * Return true if the given column is hidden (i.e. not sensible).
+ */
+ static bool IsColumnHidden(nsITreeColumn *aColumn);
+
+ /**
+ * Scroll content into view.
+ */
+ static void ScrollTo(nsIPresShell* aPresShell, nsIContent* aContent,
+ uint32_t aScrollType);
+
+ /**
+ * Return true if the given node is table header element.
+ */
+ static bool IsHTMLTableHeader(nsIContent *aContent)
+ {
+ return aContent->NodeInfo()->Equals(nsGkAtoms::th) ||
+ aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::scope);
+ }
+
+ /**
+ * Returns true if the given string is empty or contains whitespace symbols
+ * only. In contrast to nsWhitespaceTokenizer class it takes into account
+ * non-breaking space (0xa0).
+ */
+ static bool IsWhitespaceString(const nsSubstring& aString);
+
+ /**
+ * Returns true if the given character is whitespace symbol.
+ */
+ static bool IsWhitespace(char16_t aChar)
+ {
+ return aChar == ' ' || aChar == '\n' ||
+ aChar == '\r' || aChar == '\t' || aChar == 0xa0;
+ }
+
+ /*
+ * Return true if there are any observers of accessible events.
+ */
+ static bool AccEventObserversExist();
+
+ /**
+ * Notify accessible event observers of an event.
+ */
+ static void DispatchAccEvent(RefPtr<nsIAccessibleEvent> aEvent);
+
+ /**
+ * Return a role attribute on XBL bindings of the element.
+ */
+ static void XBLBindingRole(const nsIContent* aEl, nsAString& aRole);
+};
+
+#endif
diff --git a/accessible/base/nsEventShell.cpp b/accessible/base/nsEventShell.cpp
new file mode 100644
index 000000000..e070acee5
--- /dev/null
+++ b/accessible/base/nsEventShell.cpp
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsEventShell.h"
+
+#include "nsAccUtils.h"
+
+#include "mozilla/StaticPtr.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsEventShell
+////////////////////////////////////////////////////////////////////////////////
+
+void
+nsEventShell::FireEvent(AccEvent* aEvent)
+{
+ if (!aEvent || aEvent->mEventRule == AccEvent::eDoNotEmit)
+ return;
+
+ Accessible* accessible = aEvent->GetAccessible();
+ NS_ENSURE_TRUE_VOID(accessible);
+
+ nsINode* node = accessible->GetNode();
+ if (node) {
+ sEventTargetNode = node;
+ sEventFromUserInput = aEvent->IsFromUserInput();
+ }
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eEvents)) {
+ logging::MsgBegin("EVENTS", "events fired");
+ nsAutoString type;
+ GetAccService()->GetStringEventType(aEvent->GetEventType(), type);
+ logging::MsgEntry("type: %s", NS_ConvertUTF16toUTF8(type).get());
+ logging::AccessibleInfo("target", aEvent->GetAccessible());
+ logging::MsgEnd();
+ }
+#endif
+
+ accessible->HandleAccEvent(aEvent);
+ aEvent->mEventRule = AccEvent::eDoNotEmit;
+
+ sEventTargetNode = nullptr;
+}
+
+void
+nsEventShell::FireEvent(uint32_t aEventType, Accessible* aAccessible,
+ EIsFromUserInput aIsFromUserInput)
+{
+ NS_ENSURE_TRUE_VOID(aAccessible);
+
+ RefPtr<AccEvent> event = new AccEvent(aEventType, aAccessible,
+ aIsFromUserInput);
+
+ FireEvent(event);
+}
+
+void
+nsEventShell::GetEventAttributes(nsINode *aNode,
+ nsIPersistentProperties *aAttributes)
+{
+ if (aNode != sEventTargetNode)
+ return;
+
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::eventFromInput,
+ sEventFromUserInput ? NS_LITERAL_STRING("true") :
+ NS_LITERAL_STRING("false"));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsEventShell: private
+
+bool nsEventShell::sEventFromUserInput = false;
+StaticRefPtr<nsINode> nsEventShell::sEventTargetNode;
diff --git a/accessible/base/nsEventShell.h b/accessible/base/nsEventShell.h
new file mode 100644
index 000000000..169af6dad
--- /dev/null
+++ b/accessible/base/nsEventShell.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 _nsEventShell_H_
+#define _nsEventShell_H_
+
+#include "AccEvent.h"
+
+namespace mozilla {
+template<typename T> class StaticRefPtr;
+}
+class nsIPersistentProperties;
+
+/**
+ * Used for everything about events.
+ */
+class nsEventShell
+{
+public:
+
+ /**
+ * Fire the accessible event.
+ */
+ static void FireEvent(mozilla::a11y::AccEvent* aEvent);
+
+ /**
+ * Fire accessible event of the given type for the given accessible.
+ *
+ * @param aEventType [in] the event type
+ * @param aAccessible [in] the event target
+ */
+ static void FireEvent(uint32_t aEventType,
+ mozilla::a11y::Accessible* aAccessible,
+ mozilla::a11y::EIsFromUserInput aIsFromUserInput = mozilla::a11y::eAutoDetect);
+
+ /**
+ * Fire state change event.
+ */
+ static void FireEvent(mozilla::a11y::Accessible* aTarget, uint64_t aState,
+ bool aIsEnabled, bool aIsFromUserInput)
+ {
+ RefPtr<mozilla::a11y::AccStateChangeEvent> stateChangeEvent =
+ new mozilla::a11y::AccStateChangeEvent(aTarget, aState, aIsEnabled,
+ (aIsFromUserInput ?
+ mozilla::a11y::eFromUserInput :
+ mozilla::a11y::eNoUserInput));
+ FireEvent(stateChangeEvent);
+ }
+
+ /**
+ * Append 'event-from-input' object attribute if the accessible event has
+ * been fired just now for the given node.
+ *
+ * @param aNode [in] the DOM node
+ * @param aAttributes [in, out] the attributes
+ */
+ static void GetEventAttributes(nsINode *aNode,
+ nsIPersistentProperties *aAttributes);
+
+private:
+ static mozilla::StaticRefPtr<nsINode> sEventTargetNode;
+ static bool sEventFromUserInput;
+};
+
+#endif
diff --git a/accessible/base/nsTextEquivUtils.cpp b/accessible/base/nsTextEquivUtils.cpp
new file mode 100644
index 000000000..bdf14d097
--- /dev/null
+++ b/accessible/base/nsTextEquivUtils.cpp
@@ -0,0 +1,364 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 "nsTextEquivUtils.h"
+
+#include "Accessible-inl.h"
+#include "AccIterator.h"
+#include "nsCoreUtils.h"
+#include "nsIDOMXULLabeledControlEl.h"
+
+using namespace mozilla::a11y;
+
+/**
+ * The accessible for which we are computing a text equivalent. It is useful
+ * for bailing out during recursive text computation, or for special cases
+ * like step f. of the ARIA implementation guide.
+ */
+static Accessible* sInitiatorAcc = nullptr;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsTextEquivUtils. Public.
+
+nsresult
+nsTextEquivUtils::GetNameFromSubtree(Accessible* aAccessible,
+ nsAString& aName)
+{
+ aName.Truncate();
+
+ if (sInitiatorAcc)
+ return NS_OK;
+
+ sInitiatorAcc = aAccessible;
+ if (GetRoleRule(aAccessible->Role()) == eNameFromSubtreeRule) {
+ //XXX: is it necessary to care the accessible is not a document?
+ if (aAccessible->IsContent()) {
+ nsAutoString name;
+ AppendFromAccessibleChildren(aAccessible, &name);
+ name.CompressWhitespace();
+ if (!nsCoreUtils::IsWhitespaceString(name))
+ aName = name;
+ }
+ }
+
+ sInitiatorAcc = nullptr;
+
+ return NS_OK;
+}
+
+nsresult
+nsTextEquivUtils::GetTextEquivFromIDRefs(Accessible* aAccessible,
+ nsIAtom *aIDRefsAttr,
+ nsAString& aTextEquiv)
+{
+ aTextEquiv.Truncate();
+
+ nsIContent* content = aAccessible->GetContent();
+ if (!content)
+ return NS_OK;
+
+ nsIContent* refContent = nullptr;
+ IDRefsIterator iter(aAccessible->Document(), content, aIDRefsAttr);
+ while ((refContent = iter.NextElem())) {
+ if (!aTextEquiv.IsEmpty())
+ aTextEquiv += ' ';
+
+ nsresult rv = AppendTextEquivFromContent(aAccessible, refContent,
+ &aTextEquiv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsTextEquivUtils::AppendTextEquivFromContent(Accessible* aInitiatorAcc,
+ nsIContent *aContent,
+ nsAString *aString)
+{
+ // Prevent recursion which can cause infinite loops.
+ if (sInitiatorAcc)
+ return NS_OK;
+
+ sInitiatorAcc = aInitiatorAcc;
+
+ // If the given content is not visible or isn't accessible then go down
+ // through the DOM subtree otherwise go down through accessible subtree and
+ // calculate the flat string.
+ nsIFrame *frame = aContent->GetPrimaryFrame();
+ bool isVisible = frame && frame->StyleVisibility()->IsVisible();
+
+ nsresult rv = NS_ERROR_FAILURE;
+ bool goThroughDOMSubtree = true;
+
+ if (isVisible) {
+ Accessible* accessible =
+ sInitiatorAcc->Document()->GetAccessible(aContent);
+ if (accessible) {
+ rv = AppendFromAccessible(accessible, aString);
+ goThroughDOMSubtree = false;
+ }
+ }
+
+ if (goThroughDOMSubtree)
+ rv = AppendFromDOMNode(aContent, aString);
+
+ sInitiatorAcc = nullptr;
+ return rv;
+}
+
+nsresult
+nsTextEquivUtils::AppendTextEquivFromTextContent(nsIContent *aContent,
+ nsAString *aString)
+{
+ if (aContent->IsNodeOfType(nsINode::eTEXT)) {
+ bool isHTMLBlock = false;
+
+ nsIContent *parentContent = aContent->GetFlattenedTreeParent();
+ if (parentContent) {
+ nsIFrame *frame = parentContent->GetPrimaryFrame();
+ if (frame) {
+ // If this text is inside a block level frame (as opposed to span
+ // level), we need to add spaces around that block's text, so we don't
+ // get words jammed together in final name.
+ const nsStyleDisplay* display = frame->StyleDisplay();
+ if (display->IsBlockOutsideStyle() ||
+ display->mDisplay == StyleDisplay::TableCell) {
+ isHTMLBlock = true;
+ if (!aString->IsEmpty()) {
+ aString->Append(char16_t(' '));
+ }
+ }
+ }
+ }
+
+ if (aContent->TextLength() > 0) {
+ nsIFrame *frame = aContent->GetPrimaryFrame();
+ if (frame) {
+ nsIFrame::RenderedText text = frame->GetRenderedText(0,
+ UINT32_MAX, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
+ nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
+ aString->Append(text.mString);
+ } else {
+ // If aContent is an object that is display: none, we have no a frame.
+ aContent->AppendTextTo(*aString);
+ }
+ if (isHTMLBlock && !aString->IsEmpty()) {
+ aString->Append(char16_t(' '));
+ }
+ }
+
+ return NS_OK;
+ }
+
+ if (aContent->IsHTMLElement() &&
+ aContent->NodeInfo()->Equals(nsGkAtoms::br)) {
+ aString->AppendLiteral("\r\n");
+ return NS_OK;
+ }
+
+ return NS_OK_NO_NAME_CLAUSE_HANDLED;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsTextEquivUtils. Private.
+
+nsresult
+nsTextEquivUtils::AppendFromAccessibleChildren(Accessible* aAccessible,
+ nsAString *aString)
+{
+ nsresult rv = NS_OK_NO_NAME_CLAUSE_HANDLED;
+
+ uint32_t childCount = aAccessible->ChildCount();
+ for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
+ Accessible* child = aAccessible->GetChildAt(childIdx);
+ rv = AppendFromAccessible(child, aString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return rv;
+}
+
+nsresult
+nsTextEquivUtils::AppendFromAccessible(Accessible* aAccessible,
+ nsAString *aString)
+{
+ //XXX: is it necessary to care the accessible is not a document?
+ if (aAccessible->IsContent()) {
+ nsresult rv = AppendTextEquivFromTextContent(aAccessible->GetContent(),
+ aString);
+ if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
+ return rv;
+ }
+
+ bool isEmptyTextEquiv = true;
+
+ // If the name is from tooltip then append it to result string in the end
+ // (see h. step of name computation guide).
+ nsAutoString text;
+ if (aAccessible->Name(text) != eNameFromTooltip)
+ isEmptyTextEquiv = !AppendString(aString, text);
+
+ // Implementation of f. step.
+ nsresult rv = AppendFromValue(aAccessible, aString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
+ isEmptyTextEquiv = false;
+
+ // Implementation of g) step of text equivalent computation guide. Go down
+ // into subtree if accessible allows "text equivalent from subtree rule" or
+ // it's not root and not control.
+ if (isEmptyTextEquiv) {
+ uint32_t nameRule = GetRoleRule(aAccessible->Role());
+ if (nameRule & eNameFromSubtreeIfReqRule) {
+ rv = AppendFromAccessibleChildren(aAccessible, aString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
+ isEmptyTextEquiv = false;
+ }
+ }
+
+ // Implementation of h. step
+ if (isEmptyTextEquiv && !text.IsEmpty()) {
+ AppendString(aString, text);
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+nsresult
+nsTextEquivUtils::AppendFromValue(Accessible* aAccessible,
+ nsAString *aString)
+{
+ if (GetRoleRule(aAccessible->Role()) != eNameFromValueRule)
+ return NS_OK_NO_NAME_CLAUSE_HANDLED;
+
+ // Implementation of step f. of text equivalent computation. If the given
+ // accessible is not root accessible (the accessible the text equivalent is
+ // computed for in the end) then append accessible value. Otherwise append
+ // value if and only if the given accessible is in the middle of its parent.
+
+ nsAutoString text;
+ if (aAccessible != sInitiatorAcc) {
+ aAccessible->Value(text);
+
+ return AppendString(aString, text) ?
+ NS_OK : NS_OK_NO_NAME_CLAUSE_HANDLED;
+ }
+
+ //XXX: is it necessary to care the accessible is not a document?
+ if (aAccessible->IsDoc())
+ return NS_ERROR_UNEXPECTED;
+
+ nsIContent *content = aAccessible->GetContent();
+
+ for (nsIContent* childContent = content->GetPreviousSibling(); childContent;
+ childContent = childContent->GetPreviousSibling()) {
+ // check for preceding text...
+ if (!childContent->TextIsOnlyWhitespace()) {
+ for (nsIContent* siblingContent = content->GetNextSibling(); siblingContent;
+ siblingContent = siblingContent->GetNextSibling()) {
+ // .. and subsequent text
+ if (!siblingContent->TextIsOnlyWhitespace()) {
+ aAccessible->Value(text);
+
+ return AppendString(aString, text) ?
+ NS_OK : NS_OK_NO_NAME_CLAUSE_HANDLED;
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ return NS_OK_NO_NAME_CLAUSE_HANDLED;
+}
+
+nsresult
+nsTextEquivUtils::AppendFromDOMChildren(nsIContent *aContent,
+ nsAString *aString)
+{
+ for (nsIContent* childContent = aContent->GetFirstChild(); childContent;
+ childContent = childContent->GetNextSibling()) {
+ nsresult rv = AppendFromDOMNode(childContent, aString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsTextEquivUtils::AppendFromDOMNode(nsIContent *aContent, nsAString *aString)
+{
+ nsresult rv = AppendTextEquivFromTextContent(aContent, aString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
+ return NS_OK;
+
+ if (aContent->IsXULElement()) {
+ nsAutoString textEquivalent;
+ nsCOMPtr<nsIDOMXULLabeledControlElement> labeledEl =
+ do_QueryInterface(aContent);
+
+ if (labeledEl) {
+ labeledEl->GetLabel(textEquivalent);
+ } else {
+ if (aContent->NodeInfo()->Equals(nsGkAtoms::label,
+ kNameSpaceID_XUL))
+ aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value,
+ textEquivalent);
+
+ if (textEquivalent.IsEmpty())
+ aContent->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::tooltiptext, textEquivalent);
+ }
+
+ AppendString(aString, textEquivalent);
+ }
+
+ return AppendFromDOMChildren(aContent, aString);
+}
+
+bool
+nsTextEquivUtils::AppendString(nsAString *aString,
+ const nsAString& aTextEquivalent)
+{
+ if (aTextEquivalent.IsEmpty())
+ return false;
+
+ // Insert spaces to insure that words from controls aren't jammed together.
+ if (!aString->IsEmpty() && !nsCoreUtils::IsWhitespace(aString->Last()))
+ aString->Append(char16_t(' '));
+
+ aString->Append(aTextEquivalent);
+
+ if (!nsCoreUtils::IsWhitespace(aString->Last()))
+ aString->Append(char16_t(' '));
+
+ return true;
+}
+
+uint32_t
+nsTextEquivUtils::GetRoleRule(role aRole)
+{
+#define ROLE(geckoRole, stringRole, atkRole, \
+ macRole, msaaRole, ia2Role, nameRule) \
+ case roles::geckoRole: \
+ return nameRule;
+
+ switch (aRole) {
+#include "RoleMap.h"
+ default:
+ MOZ_CRASH("Unknown role.");
+ }
+
+#undef ROLE
+}
diff --git a/accessible/base/nsTextEquivUtils.h b/accessible/base/nsTextEquivUtils.h
new file mode 100644
index 000000000..3c35d0c07
--- /dev/null
+++ b/accessible/base/nsTextEquivUtils.h
@@ -0,0 +1,162 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 _nsTextEquivUtils_H_
+#define _nsTextEquivUtils_H_
+
+#include "Accessible.h"
+#include "Role.h"
+
+class nsIContent;
+
+/**
+ * Text equivalent computation rules (see nsTextEquivUtils::gRoleToNameRulesMap)
+ */
+enum ETextEquivRule
+{
+ // No rule.
+ eNoNameRule = 0x00,
+
+ // Walk into subtree only if the currently navigated accessible is not root
+ // accessible (i.e. if the accessible is part of text equivalent computation).
+ eNameFromSubtreeIfReqRule = 0x01,
+
+ // Text equivalent computation from subtree is allowed.
+ eNameFromSubtreeRule = 0x03,
+
+ // The accessible allows to append its value to text equivalent.
+ // XXX: This is temporary solution. Once we move accessible value of links
+ // and linkable accessibles to MSAA part we can remove this.
+ eNameFromValueRule = 0x04
+};
+
+/**
+ * The class provides utils methods to compute the accessible name and
+ * description.
+ */
+class nsTextEquivUtils
+{
+public:
+ typedef mozilla::a11y::Accessible Accessible;
+
+ /**
+ * Determines if the accessible has a given name rule.
+ *
+ * @param aAccessible [in] the given accessible
+ * @param aRule [in] a given name rule
+ * @return true if the accessible has the rule
+ */
+ static inline bool HasNameRule(Accessible* aAccessible, ETextEquivRule aRule)
+ {
+ return (GetRoleRule(aAccessible->Role()) & aRule) == aRule;
+ }
+
+ /**
+ * Calculates the name from accessible subtree if allowed.
+ *
+ * @param aAccessible [in] the given accessible
+ * @param aName [out] accessible name
+ */
+ static nsresult GetNameFromSubtree(Accessible* aAccessible,
+ nsAString& aName);
+
+ /**
+ * Calculates text equivalent from the subtree. Similar to GetNameFromSubtree.
+ * However it returns not empty result for things like HTML p.
+ */
+ static void GetTextEquivFromSubtree(Accessible* aAccessible,
+ nsString& aTextEquiv)
+ {
+ aTextEquiv.Truncate();
+
+ AppendFromAccessibleChildren(aAccessible, &aTextEquiv);
+ aTextEquiv.CompressWhitespace();
+ }
+
+ /**
+ * Calculates text equivalent for the given accessible from its IDRefs
+ * attribute (like aria-labelledby or aria-describedby).
+ *
+ * @param aAccessible [in] the accessible text equivalent is computed for
+ * @param aIDRefsAttr [in] IDRefs attribute on DOM node of the accessible
+ * @param aTextEquiv [out] result text equivalent
+ */
+ static nsresult GetTextEquivFromIDRefs(Accessible* aAccessible,
+ nsIAtom *aIDRefsAttr,
+ nsAString& aTextEquiv);
+
+ /**
+ * Calculates the text equivalent from the given content and its subtree if
+ * allowed and appends it to the given string.
+ *
+ * @param aInitiatorAcc [in] the accessible text equivalent is computed for
+ * in the end (root accessible of text equivalent
+ * calculation recursion)
+ * @param aContent [in] the given content the text equivalent is
+ * computed from
+ * @param aString [in, out] the string
+ */
+ static nsresult AppendTextEquivFromContent(Accessible* aInitiatorAcc,
+ nsIContent *aContent,
+ nsAString *aString);
+
+ /**
+ * Calculates the text equivalent from the given text content (may be text
+ * node or html:br) and appends it to the given string.
+ *
+ * @param aContent [in] the text content
+ * @param aString [in, out] the string
+ */
+ static nsresult AppendTextEquivFromTextContent(nsIContent *aContent,
+ nsAString *aString);
+
+private:
+ /**
+ * Iterates accessible children and calculates text equivalent from each
+ * child.
+ */
+ static nsresult AppendFromAccessibleChildren(Accessible* aAccessible,
+ nsAString *aString);
+
+ /**
+ * Calculates text equivalent from the given accessible and its subtree if
+ * allowed.
+ */
+ static nsresult AppendFromAccessible(Accessible* aAccessible,
+ nsAString *aString);
+
+ /**
+ * Calculates text equivalent from the value of given accessible.
+ */
+ static nsresult AppendFromValue(Accessible* aAccessible,
+ nsAString *aString);
+ /**
+ * Iterates DOM children and calculates text equivalent from each child node.
+ */
+ static nsresult AppendFromDOMChildren(nsIContent *aContent,
+ nsAString *aString);
+
+ /**
+ * Calculates text equivalent from the given DOM node and its subtree if
+ * allowed.
+ */
+ static nsresult AppendFromDOMNode(nsIContent *aContent, nsAString *aString);
+
+ /**
+ * Concatenates strings and appends space between them. Returns true if
+ * text equivalent string was appended.
+ */
+ static bool AppendString(nsAString *aString,
+ const nsAString& aTextEquivalent);
+
+ /**
+ * Returns the rule (constant of ETextEquivRule) for a given role.
+ */
+ static uint32_t GetRoleRule(mozilla::a11y::roles::Role aRole);
+};
+
+#endif
diff --git a/accessible/generic/ARIAGridAccessible-inl.h b/accessible/generic/ARIAGridAccessible-inl.h
new file mode 100644
index 000000000..bb2bc9705
--- /dev/null
+++ b/accessible/generic/ARIAGridAccessible-inl.h
@@ -0,0 +1,39 @@
+/* -*- 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_ARIAGridAccessible_inl_h__
+#define mozilla_a11y_ARIAGridAccessible_inl_h__
+
+#include "ARIAGridAccessible.h"
+
+#include "AccIterator.h"
+#include "nsAccUtils.h"
+
+namespace mozilla {
+namespace a11y {
+
+inline int32_t
+ARIAGridCellAccessible::RowIndexFor(Accessible* aRow) const
+{
+ Accessible* table = nsAccUtils::TableFor(aRow);
+ if (table) {
+ int32_t rowIdx = 0;
+ Accessible* row = nullptr;
+ AccIterator rowIter(table, filters::GetRow);
+ while ((row = rowIter.Next()) && row != aRow)
+ rowIdx++;
+
+ if (row)
+ return rowIdx;
+ }
+
+ return -1;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/generic/ARIAGridAccessible.cpp b/accessible/generic/ARIAGridAccessible.cpp
new file mode 100644
index 000000000..48de9bbf0
--- /dev/null
+++ b/accessible/generic/ARIAGridAccessible.cpp
@@ -0,0 +1,702 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "ARIAGridAccessible-inl.h"
+
+#include "Accessible-inl.h"
+#include "AccIterator.h"
+#include "nsAccUtils.h"
+#include "Role.h"
+#include "States.h"
+
+#include "nsIMutableArray.h"
+#include "nsIPersistentProperties2.h"
+#include "nsComponentManagerUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// ARIAGridAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Constructor
+
+ARIAGridAccessible::
+ ARIAGridAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(ARIAGridAccessible, Accessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// Table
+
+uint32_t
+ARIAGridAccessible::ColCount()
+{
+ AccIterator rowIter(this, filters::GetRow);
+ Accessible* row = rowIter.Next();
+ if (!row)
+ return 0;
+
+ AccIterator cellIter(row, filters::GetCell);
+ Accessible* cell = nullptr;
+
+ uint32_t colCount = 0;
+ while ((cell = cellIter.Next()))
+ colCount++;
+
+ return colCount;
+}
+
+uint32_t
+ARIAGridAccessible::RowCount()
+{
+ uint32_t rowCount = 0;
+ AccIterator rowIter(this, filters::GetRow);
+ while (rowIter.Next())
+ rowCount++;
+
+ return rowCount;
+}
+
+Accessible*
+ARIAGridAccessible::CellAt(uint32_t aRowIndex, uint32_t aColumnIndex)
+{
+ Accessible* row = GetRowAt(aRowIndex);
+ if (!row)
+ return nullptr;
+
+ return GetCellInRowAt(row, aColumnIndex);
+}
+
+bool
+ARIAGridAccessible::IsColSelected(uint32_t aColIdx)
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return false;
+
+ AccIterator rowIter(this, filters::GetRow);
+ Accessible* row = rowIter.Next();
+ if (!row)
+ return false;
+
+ do {
+ if (!nsAccUtils::IsARIASelected(row)) {
+ Accessible* cell = GetCellInRowAt(row, aColIdx);
+ if (!cell || !nsAccUtils::IsARIASelected(cell))
+ return false;
+ }
+ } while ((row = rowIter.Next()));
+
+ return true;
+}
+
+bool
+ARIAGridAccessible::IsRowSelected(uint32_t aRowIdx)
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return false;
+
+ Accessible* row = GetRowAt(aRowIdx);
+ if(!row)
+ return false;
+
+ if (!nsAccUtils::IsARIASelected(row)) {
+ AccIterator cellIter(row, filters::GetCell);
+ Accessible* cell = nullptr;
+ while ((cell = cellIter.Next())) {
+ if (!nsAccUtils::IsARIASelected(cell))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool
+ARIAGridAccessible::IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx)
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return false;
+
+ Accessible* row = GetRowAt(aRowIdx);
+ if(!row)
+ return false;
+
+ if (!nsAccUtils::IsARIASelected(row)) {
+ Accessible* cell = GetCellInRowAt(row, aColIdx);
+ if (!cell || !nsAccUtils::IsARIASelected(cell))
+ return false;
+ }
+
+ return true;
+}
+
+uint32_t
+ARIAGridAccessible::SelectedCellCount()
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return 0;
+
+ uint32_t count = 0, colCount = ColCount();
+
+ AccIterator rowIter(this, filters::GetRow);
+ Accessible* row = nullptr;
+
+ while ((row = rowIter.Next())) {
+ if (nsAccUtils::IsARIASelected(row)) {
+ count += colCount;
+ continue;
+ }
+
+ AccIterator cellIter(row, filters::GetCell);
+ Accessible* cell = nullptr;
+
+ while ((cell = cellIter.Next())) {
+ if (nsAccUtils::IsARIASelected(cell))
+ count++;
+ }
+ }
+
+ return count;
+}
+
+uint32_t
+ARIAGridAccessible::SelectedColCount()
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return 0;
+
+ uint32_t colCount = ColCount();
+ if (!colCount)
+ return 0;
+
+ AccIterator rowIter(this, filters::GetRow);
+ Accessible* row = rowIter.Next();
+ if (!row)
+ return 0;
+
+ nsTArray<bool> isColSelArray(colCount);
+ isColSelArray.AppendElements(colCount);
+ memset(isColSelArray.Elements(), true, colCount * sizeof(bool));
+
+ uint32_t selColCount = colCount;
+ do {
+ if (nsAccUtils::IsARIASelected(row))
+ continue;
+
+ AccIterator cellIter(row, filters::GetCell);
+ Accessible* cell = nullptr;
+ for (uint32_t colIdx = 0;
+ (cell = cellIter.Next()) && colIdx < colCount; colIdx++)
+ if (isColSelArray[colIdx] && !nsAccUtils::IsARIASelected(cell)) {
+ isColSelArray[colIdx] = false;
+ selColCount--;
+ }
+ } while ((row = rowIter.Next()));
+
+ return selColCount;
+}
+
+uint32_t
+ARIAGridAccessible::SelectedRowCount()
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return 0;
+
+ uint32_t count = 0;
+
+ AccIterator rowIter(this, filters::GetRow);
+ Accessible* row = nullptr;
+
+ while ((row = rowIter.Next())) {
+ if (nsAccUtils::IsARIASelected(row)) {
+ count++;
+ continue;
+ }
+
+ AccIterator cellIter(row, filters::GetCell);
+ Accessible* cell = cellIter.Next();
+ if (!cell)
+ continue;
+
+ bool isRowSelected = true;
+ do {
+ if (!nsAccUtils::IsARIASelected(cell)) {
+ isRowSelected = false;
+ break;
+ }
+ } while ((cell = cellIter.Next()));
+
+ if (isRowSelected)
+ count++;
+ }
+
+ return count;
+}
+
+void
+ARIAGridAccessible::SelectedCells(nsTArray<Accessible*>* aCells)
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return;
+
+ AccIterator rowIter(this, filters::GetRow);
+
+ Accessible* row = nullptr;
+ while ((row = rowIter.Next())) {
+ AccIterator cellIter(row, filters::GetCell);
+ Accessible* cell = nullptr;
+
+ if (nsAccUtils::IsARIASelected(row)) {
+ while ((cell = cellIter.Next()))
+ aCells->AppendElement(cell);
+
+ continue;
+ }
+
+ while ((cell = cellIter.Next())) {
+ if (nsAccUtils::IsARIASelected(cell))
+ aCells->AppendElement(cell);
+ }
+ }
+}
+
+void
+ARIAGridAccessible::SelectedCellIndices(nsTArray<uint32_t>* aCells)
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return;
+
+ uint32_t colCount = ColCount();
+
+ AccIterator rowIter(this, filters::GetRow);
+ Accessible* row = nullptr;
+ for (uint32_t rowIdx = 0; (row = rowIter.Next()); rowIdx++) {
+ if (nsAccUtils::IsARIASelected(row)) {
+ for (uint32_t colIdx = 0; colIdx < colCount; colIdx++)
+ aCells->AppendElement(rowIdx * colCount + colIdx);
+
+ continue;
+ }
+
+ AccIterator cellIter(row, filters::GetCell);
+ Accessible* cell = nullptr;
+ for (uint32_t colIdx = 0; (cell = cellIter.Next()); colIdx++) {
+ if (nsAccUtils::IsARIASelected(cell))
+ aCells->AppendElement(rowIdx * colCount + colIdx);
+ }
+ }
+}
+
+void
+ARIAGridAccessible::SelectedColIndices(nsTArray<uint32_t>* aCols)
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return;
+
+ uint32_t colCount = ColCount();
+ if (!colCount)
+ return;
+
+ AccIterator rowIter(this, filters::GetRow);
+ Accessible* row = rowIter.Next();
+ if (!row)
+ return;
+
+ nsTArray<bool> isColSelArray(colCount);
+ isColSelArray.AppendElements(colCount);
+ memset(isColSelArray.Elements(), true, colCount * sizeof(bool));
+
+ do {
+ if (nsAccUtils::IsARIASelected(row))
+ continue;
+
+ AccIterator cellIter(row, filters::GetCell);
+ Accessible* cell = nullptr;
+ for (uint32_t colIdx = 0;
+ (cell = cellIter.Next()) && colIdx < colCount; colIdx++)
+ if (isColSelArray[colIdx] && !nsAccUtils::IsARIASelected(cell)) {
+ isColSelArray[colIdx] = false;
+ }
+ } while ((row = rowIter.Next()));
+
+ for (uint32_t colIdx = 0; colIdx < colCount; colIdx++)
+ if (isColSelArray[colIdx])
+ aCols->AppendElement(colIdx);
+}
+
+void
+ARIAGridAccessible::SelectedRowIndices(nsTArray<uint32_t>* aRows)
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return;
+
+ AccIterator rowIter(this, filters::GetRow);
+ Accessible* row = nullptr;
+ for (uint32_t rowIdx = 0; (row = rowIter.Next()); rowIdx++) {
+ if (nsAccUtils::IsARIASelected(row)) {
+ aRows->AppendElement(rowIdx);
+ continue;
+ }
+
+ AccIterator cellIter(row, filters::GetCell);
+ Accessible* cell = cellIter.Next();
+ if (!cell)
+ continue;
+
+ bool isRowSelected = true;
+ do {
+ if (!nsAccUtils::IsARIASelected(cell)) {
+ isRowSelected = false;
+ break;
+ }
+ } while ((cell = cellIter.Next()));
+
+ if (isRowSelected)
+ aRows->AppendElement(rowIdx);
+ }
+}
+
+void
+ARIAGridAccessible::SelectRow(uint32_t aRowIdx)
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return;
+
+ AccIterator rowIter(this, filters::GetRow);
+
+ Accessible* row = nullptr;
+ for (uint32_t rowIdx = 0; (row = rowIter.Next()); rowIdx++) {
+ DebugOnly<nsresult> rv = SetARIASelected(row, rowIdx == aRowIdx);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "SetARIASelected() Shouldn't fail!");
+ }
+}
+
+void
+ARIAGridAccessible::SelectCol(uint32_t aColIdx)
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return;
+
+ AccIterator rowIter(this, filters::GetRow);
+
+ Accessible* row = nullptr;
+ while ((row = rowIter.Next())) {
+ // Unselect all cells in the row.
+ DebugOnly<nsresult> rv = SetARIASelected(row, false);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "SetARIASelected() Shouldn't fail!");
+
+ // Select cell at the column index.
+ Accessible* cell = GetCellInRowAt(row, aColIdx);
+ if (cell)
+ SetARIASelected(cell, true);
+ }
+}
+
+void
+ARIAGridAccessible::UnselectRow(uint32_t aRowIdx)
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return;
+
+ Accessible* row = GetRowAt(aRowIdx);
+ if (row)
+ SetARIASelected(row, false);
+}
+
+void
+ARIAGridAccessible::UnselectCol(uint32_t aColIdx)
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return;
+
+ AccIterator rowIter(this, filters::GetRow);
+
+ Accessible* row = nullptr;
+ while ((row = rowIter.Next())) {
+ Accessible* cell = GetCellInRowAt(row, aColIdx);
+ if (cell)
+ SetARIASelected(cell, false);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Protected
+
+Accessible*
+ARIAGridAccessible::GetRowAt(int32_t aRow)
+{
+ int32_t rowIdx = aRow;
+
+ AccIterator rowIter(this, filters::GetRow);
+
+ Accessible* row = rowIter.Next();
+ while (rowIdx != 0 && (row = rowIter.Next()))
+ rowIdx--;
+
+ return row;
+}
+
+Accessible*
+ARIAGridAccessible::GetCellInRowAt(Accessible* aRow, int32_t aColumn)
+{
+ int32_t colIdx = aColumn;
+
+ AccIterator cellIter(aRow, filters::GetCell);
+ Accessible* cell = cellIter.Next();
+ while (colIdx != 0 && (cell = cellIter.Next()))
+ colIdx--;
+
+ return cell;
+}
+
+nsresult
+ARIAGridAccessible::SetARIASelected(Accessible* aAccessible,
+ bool aIsSelected, bool aNotify)
+{
+ if (IsARIARole(nsGkAtoms::table))
+ return NS_OK;
+
+ nsIContent *content = aAccessible->GetContent();
+ NS_ENSURE_STATE(content);
+
+ nsresult rv = NS_OK;
+ if (aIsSelected)
+ rv = content->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_selected,
+ NS_LITERAL_STRING("true"), aNotify);
+ else
+ rv = content->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_selected,
+ NS_LITERAL_STRING("false"), aNotify);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // No "smart" select/unselect for internal call.
+ if (!aNotify)
+ return NS_OK;
+
+ // If row or cell accessible was selected then we're able to not bother about
+ // selection of its cells or its row because our algorithm is row oriented,
+ // i.e. we check selection on row firstly and then on cells.
+ if (aIsSelected)
+ return NS_OK;
+
+ roles::Role role = aAccessible->Role();
+
+ // If the given accessible is row that was unselected then remove
+ // aria-selected from cell accessible.
+ if (role == roles::ROW) {
+ AccIterator cellIter(aAccessible, filters::GetCell);
+ Accessible* cell = nullptr;
+
+ while ((cell = cellIter.Next())) {
+ rv = SetARIASelected(cell, false, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+ }
+
+ // If the given accessible is cell that was unselected and its row is selected
+ // then remove aria-selected from row and put aria-selected on
+ // siblings cells.
+ if (role == roles::GRID_CELL || role == roles::ROWHEADER ||
+ role == roles::COLUMNHEADER) {
+ Accessible* row = aAccessible->Parent();
+
+ if (row && row->Role() == roles::ROW &&
+ nsAccUtils::IsARIASelected(row)) {
+ rv = SetARIASelected(row, false, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AccIterator cellIter(row, filters::GetCell);
+ Accessible* cell = nullptr;
+ while ((cell = cellIter.Next())) {
+ if (cell != aAccessible) {
+ rv = SetARIASelected(cell, true, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// ARIARowAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+ARIARowAccessible::
+ ARIARowAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+ mGenericTypes |= eTableRow;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(ARIARowAccessible, Accessible)
+
+GroupPos
+ARIARowAccessible::GroupPosition()
+{
+ int32_t count = 0, index = 0;
+ Accessible* table = nsAccUtils::TableFor(this);
+ if (table && nsCoreUtils::GetUIntAttr(table->GetContent(),
+ nsGkAtoms::aria_rowcount, &count) &&
+ nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_rowindex, &index)) {
+ return GroupPos(0, index, count);
+ }
+
+ return AccessibleWrap::GroupPosition();
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// ARIAGridCellAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Constructor
+
+ARIAGridCellAccessible::
+ ARIAGridCellAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HyperTextAccessibleWrap(aContent, aDoc)
+{
+ mGenericTypes |= eTableCell;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(ARIAGridCellAccessible, HyperTextAccessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// TableCell
+
+TableAccessible*
+ARIAGridCellAccessible::Table() const
+{
+ Accessible* table = nsAccUtils::TableFor(Row());
+ return table ? table->AsTable() : nullptr;
+}
+
+uint32_t
+ARIAGridCellAccessible::ColIdx() const
+{
+ Accessible* row = Row();
+ if (!row)
+ return 0;
+
+ int32_t indexInRow = IndexInParent();
+ uint32_t colIdx = 0;
+ for (int32_t idx = 0; idx < indexInRow; idx++) {
+ Accessible* cell = row->GetChildAt(idx);
+ roles::Role role = cell->Role();
+ if (role == roles::CELL || role == roles::GRID_CELL ||
+ role == roles::ROWHEADER || role == roles::COLUMNHEADER)
+ colIdx++;
+ }
+
+ return colIdx;
+}
+
+uint32_t
+ARIAGridCellAccessible::RowIdx() const
+{
+ return RowIndexFor(Row());
+}
+
+bool
+ARIAGridCellAccessible::Selected()
+{
+ Accessible* row = Row();
+ if (!row)
+ return false;
+
+ return nsAccUtils::IsARIASelected(row) || nsAccUtils::IsARIASelected(this);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible
+
+void
+ARIAGridCellAccessible::ApplyARIAState(uint64_t* aState) const
+{
+ HyperTextAccessibleWrap::ApplyARIAState(aState);
+
+ // Return if the gridcell has aria-selected="true".
+ if (*aState & states::SELECTED)
+ return;
+
+ // Check aria-selected="true" on the row.
+ Accessible* row = Parent();
+ if (!row || row->Role() != roles::ROW)
+ return;
+
+ nsIContent *rowContent = row->GetContent();
+ if (nsAccUtils::HasDefinedARIAToken(rowContent,
+ nsGkAtoms::aria_selected) &&
+ !rowContent->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::aria_selected,
+ nsGkAtoms::_false, eCaseMatters))
+ *aState |= states::SELECTABLE | states::SELECTED;
+}
+
+already_AddRefed<nsIPersistentProperties>
+ARIAGridCellAccessible::NativeAttributes()
+{
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ HyperTextAccessibleWrap::NativeAttributes();
+
+ // Expose "table-cell-index" attribute.
+ Accessible* thisRow = Row();
+ if (!thisRow)
+ return attributes.forget();
+
+ int32_t colIdx = 0, colCount = 0;
+ uint32_t childCount = thisRow->ChildCount();
+ for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
+ Accessible* child = thisRow->GetChildAt(childIdx);
+ if (child == this)
+ colIdx = colCount;
+
+ roles::Role role = child->Role();
+ if (role == roles::CELL || role == roles::GRID_CELL ||
+ role == roles::ROWHEADER || role == roles::COLUMNHEADER)
+ colCount++;
+ }
+
+ int32_t rowIdx = RowIndexFor(thisRow);
+
+ nsAutoString stringIdx;
+ stringIdx.AppendInt(rowIdx * colCount + colIdx);
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::tableCellIndex, stringIdx);
+
+#ifdef DEBUG
+ nsAutoString unused;
+ attributes->SetStringProperty(NS_LITERAL_CSTRING("cppclass"),
+ NS_LITERAL_STRING("ARIAGridCellAccessible"),
+ unused);
+#endif
+
+ return attributes.forget();
+}
+
+GroupPos
+ARIAGridCellAccessible::GroupPosition()
+{
+ int32_t count = 0, index = 0;
+ TableAccessible* table = Table();
+ if (table && nsCoreUtils::GetUIntAttr(table->AsAccessible()->GetContent(),
+ nsGkAtoms::aria_colcount, &count) &&
+ nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_colindex, &index)) {
+ return GroupPos(0, index, count);
+ }
+
+ return GroupPos();
+}
diff --git a/accessible/generic/ARIAGridAccessible.h b/accessible/generic/ARIAGridAccessible.h
new file mode 100644
index 000000000..c9a36cc6e
--- /dev/null
+++ b/accessible/generic/ARIAGridAccessible.h
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_ARIAGridAccessible_h_
+#define MOZILLA_A11Y_ARIAGridAccessible_h_
+
+#include "HyperTextAccessibleWrap.h"
+#include "TableAccessible.h"
+#include "TableCellAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Accessible for ARIA grid and treegrid.
+ */
+class ARIAGridAccessible : public AccessibleWrap,
+ public TableAccessible
+{
+public:
+ ARIAGridAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual TableAccessible* AsTable() override { return this; }
+
+ // TableAccessible
+ virtual uint32_t ColCount() override;
+ virtual uint32_t RowCount() override;
+ virtual Accessible* CellAt(uint32_t aRowIndex, uint32_t aColumnIndex) override;
+ virtual bool IsColSelected(uint32_t aColIdx) override;
+ virtual bool IsRowSelected(uint32_t aRowIdx) override;
+ virtual bool IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) override;
+ virtual uint32_t SelectedCellCount() override;
+ virtual uint32_t SelectedColCount() override;
+ virtual uint32_t SelectedRowCount() override;
+ virtual void SelectedCells(nsTArray<Accessible*>* aCells) override;
+ virtual void SelectedCellIndices(nsTArray<uint32_t>* aCells) override;
+ virtual void SelectedColIndices(nsTArray<uint32_t>* aCols) override;
+ virtual void SelectedRowIndices(nsTArray<uint32_t>* aRows) override;
+ virtual void SelectCol(uint32_t aColIdx) override;
+ virtual void SelectRow(uint32_t aRowIdx) override;
+ virtual void UnselectCol(uint32_t aColIdx) override;
+ virtual void UnselectRow(uint32_t aRowIdx) override;
+ virtual Accessible* AsAccessible() override { return this; }
+
+protected:
+ virtual ~ARIAGridAccessible() {}
+
+ /**
+ * Return row accessible at the given row index.
+ */
+ Accessible* GetRowAt(int32_t aRow);
+
+ /**
+ * Return cell accessible at the given column index in the row.
+ */
+ Accessible* GetCellInRowAt(Accessible* aRow, int32_t aColumn);
+
+ /**
+ * Set aria-selected attribute value on DOM node of the given accessible.
+ *
+ * @param aAccessible [in] accessible
+ * @param aIsSelected [in] new value of aria-selected attribute
+ * @param aNotify [in, optional] specifies if DOM should be notified
+ * about attribute change (used internally).
+ */
+ nsresult SetARIASelected(Accessible* aAccessible, bool aIsSelected,
+ bool aNotify = true);
+};
+
+
+/**
+ * Accessible for ARIA row.
+ */
+class ARIARowAccessible : public AccessibleWrap
+{
+public:
+ ARIARowAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual mozilla::a11y::GroupPos GroupPosition() override;
+
+protected:
+ virtual ~ARIARowAccessible() {}
+};
+
+
+/**
+ * Accessible for ARIA gridcell and rowheader/columnheader.
+ */
+class ARIAGridCellAccessible : public HyperTextAccessibleWrap,
+ public TableCellAccessible
+{
+public:
+ ARIAGridCellAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual TableCellAccessible* AsTableCell() override { return this; }
+ virtual void ApplyARIAState(uint64_t* aState) const override;
+ virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override;
+ virtual mozilla::a11y::GroupPos GroupPosition() override;
+
+protected:
+ virtual ~ARIAGridCellAccessible() {}
+
+ /**
+ * Return a containing row.
+ */
+ Accessible* Row() const
+ {
+ Accessible* row = Parent();
+ return row && row->IsTableRow() ? row : nullptr;
+ }
+
+ /**
+ * Return index of the given row.
+ */
+ int32_t RowIndexFor(Accessible* aRow) const;
+
+ // TableCellAccessible
+ virtual TableAccessible* Table() const override;
+ virtual uint32_t ColIdx() const override;
+ virtual uint32_t RowIdx() const override;
+ virtual bool Selected() override;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/generic/Accessible-inl.h b/accessible/generic/Accessible-inl.h
new file mode 100644
index 000000000..f80056479
--- /dev/null
+++ b/accessible/generic/Accessible-inl.h
@@ -0,0 +1,135 @@
+/* -*- 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_Accessible_inl_h_
+#define mozilla_a11y_Accessible_inl_h_
+
+#include "DocAccessible.h"
+#include "ARIAMap.h"
+#include "nsCoreUtils.h"
+
+#ifdef A11Y_LOG
+#include "Logging.h"
+#endif
+
+namespace mozilla {
+namespace a11y {
+
+inline mozilla::a11y::role
+Accessible::Role()
+{
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (!roleMapEntry || roleMapEntry->roleRule != kUseMapRole)
+ return ARIATransformRole(NativeRole());
+
+ return ARIATransformRole(roleMapEntry->role);
+}
+
+inline bool
+Accessible::HasARIARole() const
+{
+ return mRoleMapEntryIndex != aria::NO_ROLE_MAP_ENTRY_INDEX;
+}
+
+inline bool
+Accessible::IsARIARole(nsIAtom* aARIARole) const
+{
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ return roleMapEntry && roleMapEntry->Is(aARIARole);
+}
+
+inline bool
+Accessible::HasStrongARIARole() const
+{
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ return roleMapEntry && roleMapEntry->roleRule == kUseMapRole;
+}
+
+inline const nsRoleMapEntry*
+Accessible::ARIARoleMap() const
+{
+ return aria::GetRoleMapFromIndex(mRoleMapEntryIndex);
+}
+
+inline mozilla::a11y::role
+Accessible::ARIARole()
+{
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (!roleMapEntry || roleMapEntry->roleRule != kUseMapRole)
+ return mozilla::a11y::roles::NOTHING;
+
+ return ARIATransformRole(roleMapEntry->role);
+}
+
+inline void
+Accessible::SetRoleMapEntry(const nsRoleMapEntry* aRoleMapEntry)
+{
+ mRoleMapEntryIndex = aria::GetIndexFromRoleMap(aRoleMapEntry);
+}
+
+inline bool
+Accessible::IsSearchbox() const
+{
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ return (roleMapEntry && roleMapEntry->Is(nsGkAtoms::searchbox)) ||
+ (mContent->IsHTMLElement(nsGkAtoms::input) &&
+ mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::textInputType, eCaseMatters));
+}
+
+inline bool
+Accessible::HasGenericType(AccGenericType aType) const
+{
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ return (mGenericTypes & aType) ||
+ (roleMapEntry && roleMapEntry->IsOfType(aType));
+}
+
+inline bool
+Accessible::HasNumericValue() const
+{
+ if (mStateFlags & eHasNumericValue)
+ return true;
+
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ return roleMapEntry && roleMapEntry->valueRule != eNoValue;
+}
+
+inline void
+Accessible::ScrollTo(uint32_t aHow) const
+{
+ if (mContent)
+ nsCoreUtils::ScrollTo(mDoc->PresShell(), mContent, aHow);
+}
+
+inline bool
+Accessible::InsertAfter(Accessible* aNewChild, Accessible* aRefChild)
+{
+ MOZ_ASSERT(aNewChild, "No new child to insert");
+
+ if (aRefChild && aRefChild->Parent() != this) {
+#ifdef A11Y_LOG
+ logging::TreeInfo("broken accessible tree", 0,
+ "parent", this, "prev sibling parent",
+ aRefChild->Parent(), "child", aNewChild, nullptr);
+ if (logging::IsEnabled(logging::eVerbose)) {
+ logging::Tree("TREE", "Document tree", mDoc);
+ logging::DOMTree("TREE", "DOM document tree", mDoc);
+ }
+#endif
+ MOZ_ASSERT_UNREACHABLE("Broken accessible tree");
+ mDoc->UnbindFromDocument(aNewChild);
+ return false;
+ }
+
+ return InsertChildAt(aRefChild ? aRefChild->IndexInParent() + 1 : 0,
+ aNewChild);
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/generic/Accessible.cpp b/accessible/generic/Accessible.cpp
new file mode 100644
index 000000000..7ff2f8283
--- /dev/null
+++ b/accessible/generic/Accessible.cpp
@@ -0,0 +1,2856 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "Accessible-inl.h"
+
+#include "nsIXBLAccessible.h"
+
+#include "EmbeddedObjCollector.h"
+#include "AccGroupInfo.h"
+#include "AccIterator.h"
+#include "nsAccUtils.h"
+#include "nsAccessibilityService.h"
+#include "ApplicationAccessible.h"
+#include "NotificationController.h"
+#include "nsEventShell.h"
+#include "nsTextEquivUtils.h"
+#include "DocAccessibleChild.h"
+#include "EventTree.h"
+#include "Relation.h"
+#include "Role.h"
+#include "RootAccessible.h"
+#include "States.h"
+#include "StyleInfo.h"
+#include "TableAccessible.h"
+#include "TableCellAccessible.h"
+#include "TreeWalker.h"
+
+#include "nsIDOMElement.h"
+#include "nsIDOMNodeFilter.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsIDOMKeyEvent.h"
+#include "nsIDOMTreeWalker.h"
+#include "nsIDOMXULButtonElement.h"
+#include "nsIDOMXULDocument.h"
+#include "nsIDOMXULElement.h"
+#include "nsIDOMXULLabelElement.h"
+#include "nsIDOMXULSelectCntrlEl.h"
+#include "nsIDOMXULSelectCntrlItemEl.h"
+#include "nsPIDOMWindow.h"
+
+#include "nsIDocument.h"
+#include "nsIContent.h"
+#include "nsIForm.h"
+#include "nsIFormControl.h"
+
+#include "nsDeckFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsIPresShell.h"
+#include "nsIStringBundle.h"
+#include "nsPresContext.h"
+#include "nsIFrame.h"
+#include "nsView.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIScrollableFrame.h"
+#include "nsFocusManager.h"
+
+#include "nsXPIDLString.h"
+#include "nsUnicharUtils.h"
+#include "nsReadableUtils.h"
+#include "prdtoa.h"
+#include "nsIAtom.h"
+#include "nsIURI.h"
+#include "nsArrayUtils.h"
+#include "nsIMutableArray.h"
+#include "nsIObserverService.h"
+#include "nsIServiceManager.h"
+#include "nsWhitespaceTokenizer.h"
+#include "nsAttrName.h"
+
+#ifdef DEBUG
+#include "nsIDOMCharacterData.h"
+#endif
+
+#include "mozilla/Assertions.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/CanvasRenderingContext2D.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLCanvasElement.h"
+#include "mozilla/dom/HTMLBodyElement.h"
+#include "mozilla/dom/TreeWalker.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible: nsISupports and cycle collection
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Accessible)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Accessible)
+ tmp->Shutdown();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Accessible)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent, mDoc)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Accessible)
+ if (aIID.Equals(NS_GET_IID(Accessible)))
+ foundInterface = this;
+ else
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, Accessible)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Accessible)
+NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_DESTROY(Accessible, LastRelease())
+
+Accessible::Accessible(nsIContent* aContent, DocAccessible* aDoc) :
+ mContent(aContent), mDoc(aDoc),
+ mParent(nullptr), mIndexInParent(-1),
+ mRoleMapEntryIndex(aria::NO_ROLE_MAP_ENTRY_INDEX),
+ mStateFlags(0), mContextFlags(0), mType(0), mGenericTypes(0),
+ mReorderEventTarget(false), mShowEventTarget(false), mHideEventTarget(false)
+{
+ mBits.groupInfo = nullptr;
+ mInt.mIndexOfEmbeddedChild = -1;
+}
+
+Accessible::~Accessible()
+{
+ NS_ASSERTION(!mDoc, "LastRelease was never called!?!");
+}
+
+ENameValueFlag
+Accessible::Name(nsString& aName)
+{
+ aName.Truncate();
+
+ if (!HasOwnContent())
+ return eNameOK;
+
+ ARIAName(aName);
+ if (!aName.IsEmpty())
+ return eNameOK;
+
+ nsCOMPtr<nsIXBLAccessible> xblAccessible(do_QueryInterface(mContent));
+ if (xblAccessible) {
+ xblAccessible->GetAccessibleName(aName);
+ if (!aName.IsEmpty())
+ return eNameOK;
+ }
+
+ ENameValueFlag nameFlag = NativeName(aName);
+ if (!aName.IsEmpty())
+ return nameFlag;
+
+ // In the end get the name from tooltip.
+ if (mContent->IsHTMLElement()) {
+ if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::title, aName)) {
+ aName.CompressWhitespace();
+ return eNameFromTooltip;
+ }
+ } else if (mContent->IsXULElement()) {
+ if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::tooltiptext, aName)) {
+ aName.CompressWhitespace();
+ return eNameFromTooltip;
+ }
+ } else if (mContent->IsSVGElement()) {
+ // If user agents need to choose among multiple ‘desc’ or ‘title’ elements
+ // for processing, the user agent shall choose the first one.
+ for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
+ childElm = childElm->GetNextSibling()) {
+ if (childElm->IsSVGElement(nsGkAtoms::desc)) {
+ nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aName);
+ return eNameFromTooltip;
+ }
+ }
+ }
+
+ if (nameFlag != eNoNameOnPurpose)
+ aName.SetIsVoid(true);
+
+ return nameFlag;
+}
+
+void
+Accessible::Description(nsString& aDescription)
+{
+ // There are 4 conditions that make an accessible have no accDescription:
+ // 1. it's a text node; or
+ // 2. It has no DHTML describedby property
+ // 3. it doesn't have an accName; or
+ // 4. its title attribute already equals to its accName nsAutoString name;
+
+ if (!HasOwnContent() || mContent->IsNodeOfType(nsINode::eTEXT))
+ return;
+
+ nsTextEquivUtils::
+ GetTextEquivFromIDRefs(this, nsGkAtoms::aria_describedby,
+ aDescription);
+
+ if (aDescription.IsEmpty()) {
+ NativeDescription(aDescription);
+
+ if (aDescription.IsEmpty()) {
+ // Keep the Name() method logic.
+ if (mContent->IsHTMLElement()) {
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::title, aDescription);
+ } else if (mContent->IsXULElement()) {
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::tooltiptext, aDescription);
+ } else if (mContent->IsSVGElement()) {
+ for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
+ childElm = childElm->GetNextSibling()) {
+ if (childElm->IsSVGElement(nsGkAtoms::desc)) {
+ nsTextEquivUtils::AppendTextEquivFromContent(this, childElm,
+ &aDescription);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (!aDescription.IsEmpty()) {
+ aDescription.CompressWhitespace();
+ nsAutoString name;
+ Name(name);
+ // Don't expose a description if it is the same as the name.
+ if (aDescription.Equals(name))
+ aDescription.Truncate();
+ }
+}
+
+KeyBinding
+Accessible::AccessKey() const
+{
+ if (!HasOwnContent())
+ return KeyBinding();
+
+ uint32_t key = nsCoreUtils::GetAccessKeyFor(mContent);
+ if (!key && mContent->IsElement()) {
+ Accessible* label = nullptr;
+
+ // Copy access key from label node.
+ if (mContent->IsHTMLElement()) {
+ // Unless it is labeled via an ancestor <label>, in which case that would
+ // be redundant.
+ HTMLLabelIterator iter(Document(), this,
+ HTMLLabelIterator::eSkipAncestorLabel);
+ label = iter.Next();
+
+ } else if (mContent->IsXULElement()) {
+ XULLabelIterator iter(Document(), mContent);
+ label = iter.Next();
+ }
+
+ if (label)
+ key = nsCoreUtils::GetAccessKeyFor(label->GetContent());
+ }
+
+ if (!key)
+ return KeyBinding();
+
+ // Get modifier mask. Use ui.key.generalAccessKey (unless it is -1).
+ switch (Preferences::GetInt("ui.key.generalAccessKey", -1)) {
+ case -1:
+ break;
+ case nsIDOMKeyEvent::DOM_VK_SHIFT:
+ return KeyBinding(key, KeyBinding::kShift);
+ case nsIDOMKeyEvent::DOM_VK_CONTROL:
+ return KeyBinding(key, KeyBinding::kControl);
+ case nsIDOMKeyEvent::DOM_VK_ALT:
+ return KeyBinding(key, KeyBinding::kAlt);
+ case nsIDOMKeyEvent::DOM_VK_META:
+ return KeyBinding(key, KeyBinding::kMeta);
+ default:
+ return KeyBinding();
+ }
+
+ // Determine the access modifier used in this context.
+ nsIDocument* document = mContent->GetUncomposedDoc();
+ if (!document)
+ return KeyBinding();
+
+ nsCOMPtr<nsIDocShellTreeItem> treeItem(document->GetDocShell());
+ if (!treeItem)
+ return KeyBinding();
+
+ nsresult rv = NS_ERROR_FAILURE;
+ int32_t modifierMask = 0;
+ switch (treeItem->ItemType()) {
+ case nsIDocShellTreeItem::typeChrome:
+ rv = Preferences::GetInt("ui.key.chromeAccess", &modifierMask);
+ break;
+ case nsIDocShellTreeItem::typeContent:
+ rv = Preferences::GetInt("ui.key.contentAccess", &modifierMask);
+ break;
+ }
+
+ return NS_SUCCEEDED(rv) ? KeyBinding(key, modifierMask) : KeyBinding();
+}
+
+KeyBinding
+Accessible::KeyboardShortcut() const
+{
+ return KeyBinding();
+}
+
+void
+Accessible::TranslateString(const nsString& aKey, nsAString& aStringOut)
+{
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ services::GetStringBundleService();
+ if (!stringBundleService)
+ return;
+
+ nsCOMPtr<nsIStringBundle> stringBundle;
+ stringBundleService->CreateBundle(
+ "chrome://global-platform/locale/accessible.properties",
+ getter_AddRefs(stringBundle));
+ if (!stringBundle)
+ return;
+
+ nsXPIDLString xsValue;
+ nsresult rv = stringBundle->GetStringFromName(aKey.get(), getter_Copies(xsValue));
+ if (NS_SUCCEEDED(rv))
+ aStringOut.Assign(xsValue);
+}
+
+uint64_t
+Accessible::VisibilityState()
+{
+ nsIFrame* frame = GetFrame();
+ if (!frame)
+ return states::INVISIBLE;
+
+ // Walk the parent frame chain to see if there's invisible parent or the frame
+ // is in background tab.
+ if (!frame->StyleVisibility()->IsVisible())
+ return states::INVISIBLE;
+
+ nsIFrame* curFrame = frame;
+ do {
+ nsView* view = curFrame->GetView();
+ if (view && view->GetVisibility() == nsViewVisibility_kHide)
+ return states::INVISIBLE;
+
+ if (nsLayoutUtils::IsPopup(curFrame))
+ return 0;
+
+ // Offscreen state for background tab content and invisible for not selected
+ // deck panel.
+ nsIFrame* parentFrame = curFrame->GetParent();
+ nsDeckFrame* deckFrame = do_QueryFrame(parentFrame);
+ if (deckFrame && deckFrame->GetSelectedBox() != curFrame) {
+ if (deckFrame->GetContent()->IsXULElement(nsGkAtoms::tabpanels))
+ return states::OFFSCREEN;
+
+ NS_NOTREACHED("Children of not selected deck panel are not accessible.");
+ return states::INVISIBLE;
+ }
+
+ // If contained by scrollable frame then check that at least 12 pixels
+ // around the object is visible, otherwise the object is offscreen.
+ nsIScrollableFrame* scrollableFrame = do_QueryFrame(parentFrame);
+ if (scrollableFrame) {
+ nsRect scrollPortRect = scrollableFrame->GetScrollPortRect();
+ nsRect frameRect = nsLayoutUtils::TransformFrameRectToAncestor(
+ frame, frame->GetRectRelativeToSelf(), parentFrame);
+ if (!scrollPortRect.Contains(frameRect)) {
+ const nscoord kMinPixels = nsPresContext::CSSPixelsToAppUnits(12);
+ scrollPortRect.Deflate(kMinPixels, kMinPixels);
+ if (!scrollPortRect.Intersects(frameRect))
+ return states::OFFSCREEN;
+ }
+ }
+
+ if (!parentFrame) {
+ parentFrame = nsLayoutUtils::GetCrossDocParentFrame(curFrame);
+ if (parentFrame && !parentFrame->StyleVisibility()->IsVisible())
+ return states::INVISIBLE;
+ }
+
+ curFrame = parentFrame;
+ } while (curFrame);
+
+ // Zero area rects can occur in the first frame of a multi-frame text flow,
+ // in which case the rendered text is not empty and the frame should not be
+ // marked invisible.
+ // XXX Can we just remove this check? Why do we need to mark empty
+ // text invisible?
+ if (frame->GetType() == nsGkAtoms::textFrame &&
+ !(frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) &&
+ frame->GetRect().IsEmpty()) {
+ nsIFrame::RenderedText text = frame->GetRenderedText(0,
+ UINT32_MAX, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
+ nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
+ if (text.mString.IsEmpty()) {
+ return states::INVISIBLE;
+ }
+ }
+
+ return 0;
+}
+
+uint64_t
+Accessible::NativeState()
+{
+ uint64_t state = 0;
+
+ if (!IsInDocument())
+ state |= states::STALE;
+
+ if (HasOwnContent() && mContent->IsElement()) {
+ EventStates elementState = mContent->AsElement()->State();
+
+ if (elementState.HasState(NS_EVENT_STATE_INVALID))
+ state |= states::INVALID;
+
+ if (elementState.HasState(NS_EVENT_STATE_REQUIRED))
+ state |= states::REQUIRED;
+
+ state |= NativeInteractiveState();
+ if (FocusMgr()->IsFocused(this))
+ state |= states::FOCUSED;
+ }
+
+ // Gather states::INVISIBLE and states::OFFSCREEN flags for this object.
+ state |= VisibilityState();
+
+ nsIFrame *frame = GetFrame();
+ if (frame) {
+ if (frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW)
+ state |= states::FLOATING;
+
+ // XXX we should look at layout for non XUL box frames, but need to decide
+ // how that interacts with ARIA.
+ if (HasOwnContent() && mContent->IsXULElement() && frame->IsXULBoxFrame()) {
+ const nsStyleXUL* xulStyle = frame->StyleXUL();
+ if (xulStyle && frame->IsXULBoxFrame()) {
+ // In XUL all boxes are either vertical or horizontal
+ if (xulStyle->mBoxOrient == StyleBoxOrient::Vertical)
+ state |= states::VERTICAL;
+ else
+ state |= states::HORIZONTAL;
+ }
+ }
+ }
+
+ // Check if a XUL element has the popup attribute (an attached popup menu).
+ if (HasOwnContent() && mContent->IsXULElement() &&
+ mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::popup))
+ state |= states::HASPOPUP;
+
+ // Bypass the link states specialization for non links.
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (!roleMapEntry || roleMapEntry->roleRule == kUseNativeRole ||
+ roleMapEntry->role == roles::LINK)
+ state |= NativeLinkState();
+
+ return state;
+}
+
+uint64_t
+Accessible::NativeInteractiveState() const
+{
+ if (!mContent->IsElement())
+ return 0;
+
+ if (NativelyUnavailable())
+ return states::UNAVAILABLE;
+
+ nsIFrame* frame = GetFrame();
+ if (frame && frame->IsFocusable())
+ return states::FOCUSABLE;
+
+ return 0;
+}
+
+uint64_t
+Accessible::NativeLinkState() const
+{
+ return 0;
+}
+
+bool
+Accessible::NativelyUnavailable() const
+{
+ if (mContent->IsHTMLElement())
+ return mContent->AsElement()->State().HasState(NS_EVENT_STATE_DISABLED);
+
+ return mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters);
+}
+
+Accessible*
+Accessible::FocusedChild()
+{
+ Accessible* focus = FocusMgr()->FocusedAccessible();
+ if (focus && (focus == this || focus->Parent() == this))
+ return focus;
+
+ return nullptr;
+}
+
+Accessible*
+Accessible::ChildAtPoint(int32_t aX, int32_t aY,
+ EWhichChildAtPoint aWhichChild)
+{
+ // If we can't find the point in a child, we will return the fallback answer:
+ // we return |this| if the point is within it, otherwise nullptr.
+ Accessible* fallbackAnswer = nullptr;
+ nsIntRect rect = Bounds();
+ if (aX >= rect.x && aX < rect.x + rect.width &&
+ aY >= rect.y && aY < rect.y + rect.height)
+ fallbackAnswer = this;
+
+ if (nsAccUtils::MustPrune(this)) // Do not dig any further
+ return fallbackAnswer;
+
+ // Search an accessible at the given point starting from accessible document
+ // because containing block (see CSS2) for out of flow element (for example,
+ // absolutely positioned element) may be different from its DOM parent and
+ // therefore accessible for containing block may be different from accessible
+ // for DOM parent but GetFrameForPoint() should be called for containing block
+ // to get an out of flow element.
+ DocAccessible* accDocument = Document();
+ NS_ENSURE_TRUE(accDocument, nullptr);
+
+ nsIFrame* rootFrame = accDocument->GetFrame();
+ NS_ENSURE_TRUE(rootFrame, nullptr);
+
+ nsIFrame* startFrame = rootFrame;
+
+ // Check whether the point is at popup content.
+ nsIWidget* rootWidget = rootFrame->GetView()->GetNearestWidget(nullptr);
+ NS_ENSURE_TRUE(rootWidget, nullptr);
+
+ LayoutDeviceIntRect rootRect = rootWidget->GetScreenBounds();
+
+ WidgetMouseEvent dummyEvent(true, eMouseMove, rootWidget,
+ WidgetMouseEvent::eSynthesized);
+ dummyEvent.mRefPoint = LayoutDeviceIntPoint(aX - rootRect.x, aY - rootRect.y);
+
+ nsIFrame* popupFrame = nsLayoutUtils::
+ GetPopupFrameForEventCoordinates(accDocument->PresContext()->GetRootPresContext(),
+ &dummyEvent);
+ if (popupFrame) {
+ // If 'this' accessible is not inside the popup then ignore the popup when
+ // searching an accessible at point.
+ DocAccessible* popupDoc =
+ GetAccService()->GetDocAccessible(popupFrame->GetContent()->OwnerDoc());
+ Accessible* popupAcc =
+ popupDoc->GetAccessibleOrContainer(popupFrame->GetContent());
+ Accessible* popupChild = this;
+ while (popupChild && !popupChild->IsDoc() && popupChild != popupAcc)
+ popupChild = popupChild->Parent();
+
+ if (popupChild == popupAcc)
+ startFrame = popupFrame;
+ }
+
+ nsPresContext* presContext = startFrame->PresContext();
+ nsRect screenRect = startFrame->GetScreenRectInAppUnits();
+ nsPoint offset(presContext->DevPixelsToAppUnits(aX) - screenRect.x,
+ presContext->DevPixelsToAppUnits(aY) - screenRect.y);
+ nsIFrame* foundFrame = nsLayoutUtils::GetFrameForPoint(startFrame, offset);
+
+ nsIContent* content = nullptr;
+ if (!foundFrame || !(content = foundFrame->GetContent()))
+ return fallbackAnswer;
+
+ // Get accessible for the node with the point or the first accessible in
+ // the DOM parent chain.
+ DocAccessible* contentDocAcc = GetAccService()->
+ GetDocAccessible(content->OwnerDoc());
+
+ // contentDocAcc in some circumstances can be nullptr. See bug 729861
+ NS_ASSERTION(contentDocAcc, "could not get the document accessible");
+ if (!contentDocAcc)
+ return fallbackAnswer;
+
+ Accessible* accessible = contentDocAcc->GetAccessibleOrContainer(content);
+ if (!accessible)
+ return fallbackAnswer;
+
+ // Hurray! We have an accessible for the frame that layout gave us.
+ // Since DOM node of obtained accessible may be out of flow then we should
+ // ensure obtained accessible is a child of this accessible.
+ Accessible* child = accessible;
+ while (child != this) {
+ Accessible* parent = child->Parent();
+ if (!parent) {
+ // Reached the top of the hierarchy. These bounds were inside an
+ // accessible that is not a descendant of this one.
+ return fallbackAnswer;
+ }
+
+ // If we landed on a legitimate child of |this|, and we want the direct
+ // child, return it here.
+ if (parent == this && aWhichChild == eDirectChild)
+ return child;
+
+ child = parent;
+ }
+
+ // Manually walk through accessible children and see if the are within this
+ // point. Skip offscreen or invisible accessibles. This takes care of cases
+ // where layout won't walk into things for us, such as image map areas and
+ // sub documents (XXX: subdocuments should be handled by methods of
+ // OuterDocAccessibles).
+ uint32_t childCount = accessible->ChildCount();
+ for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
+ Accessible* child = accessible->GetChildAt(childIdx);
+
+ nsIntRect childRect = child->Bounds();
+ if (aX >= childRect.x && aX < childRect.x + childRect.width &&
+ aY >= childRect.y && aY < childRect.y + childRect.height &&
+ (child->State() & states::INVISIBLE) == 0) {
+
+ if (aWhichChild == eDeepestChild)
+ return child->ChildAtPoint(aX, aY, eDeepestChild);
+
+ return child;
+ }
+ }
+
+ return accessible;
+}
+
+nsRect
+Accessible::RelativeBounds(nsIFrame** aBoundingFrame) const
+{
+ nsIFrame* frame = GetFrame();
+ if (frame && mContent) {
+ bool* hasHitRegionRect = static_cast<bool*>(mContent->GetProperty(nsGkAtoms::hitregion));
+
+ if (hasHitRegionRect && mContent->IsElement()) {
+ // This is for canvas fallback content
+ // Find a canvas frame the found hit region is relative to.
+ nsIFrame* canvasFrame = frame->GetParent();
+ if (canvasFrame) {
+ canvasFrame = nsLayoutUtils::GetClosestFrameOfType(canvasFrame, nsGkAtoms::HTMLCanvasFrame);
+ }
+
+ // make the canvas the bounding frame
+ if (canvasFrame) {
+ *aBoundingFrame = canvasFrame;
+ dom::HTMLCanvasElement *canvas =
+ dom::HTMLCanvasElement::FromContent(canvasFrame->GetContent());
+
+ // get the bounding rect of the hit region
+ nsRect bounds;
+ if (canvas && canvas->CountContexts() &&
+ canvas->GetContextAtIndex(0)->GetHitRegionRect(mContent->AsElement(), bounds)) {
+ return bounds;
+ }
+ }
+ }
+
+ *aBoundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame);
+ return nsLayoutUtils::
+ GetAllInFlowRectsUnion(frame, *aBoundingFrame,
+ nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
+ }
+
+ return nsRect();
+}
+
+nsIntRect
+Accessible::Bounds() const
+{
+ nsIFrame* boundingFrame = nullptr;
+ nsRect unionRectTwips = RelativeBounds(&boundingFrame);
+ if (!boundingFrame)
+ return nsIntRect();
+
+ nsIntRect screenRect;
+ nsPresContext* presContext = mDoc->PresContext();
+ screenRect.x = presContext->AppUnitsToDevPixels(unionRectTwips.x);
+ screenRect.y = presContext->AppUnitsToDevPixels(unionRectTwips.y);
+ screenRect.width = presContext->AppUnitsToDevPixels(unionRectTwips.width);
+ screenRect.height = presContext->AppUnitsToDevPixels(unionRectTwips.height);
+
+ // We have the union of the rectangle, now we need to put it in absolute
+ // screen coords.
+ nsIntRect orgRectPixels = boundingFrame->GetScreenRectInAppUnits().
+ ToNearestPixels(presContext->AppUnitsPerDevPixel());
+ screenRect.x += orgRectPixels.x;
+ screenRect.y += orgRectPixels.y;
+
+ return screenRect;
+}
+
+void
+Accessible::SetSelected(bool aSelect)
+{
+ if (!HasOwnContent())
+ return;
+
+ Accessible* select = nsAccUtils::GetSelectableContainer(this, State());
+ if (select) {
+ if (select->State() & states::MULTISELECTABLE) {
+ if (ARIARoleMap()) {
+ if (aSelect) {
+ mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_selected,
+ NS_LITERAL_STRING("true"), true);
+ } else {
+ mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::aria_selected, true);
+ }
+ }
+ return;
+ }
+
+ if (aSelect)
+ TakeFocus();
+ }
+}
+
+void
+Accessible::TakeSelection()
+{
+ Accessible* select = nsAccUtils::GetSelectableContainer(this, State());
+ if (select) {
+ if (select->State() & states::MULTISELECTABLE)
+ select->UnselectAll();
+ SetSelected(true);
+ }
+}
+
+void
+Accessible::TakeFocus()
+{
+ nsIFrame* frame = GetFrame();
+ if (!frame)
+ return;
+
+ nsIContent* focusContent = mContent;
+
+ // If the accessible focus is managed by container widget then focus the
+ // widget and set the accessible as its current item.
+ if (!frame->IsFocusable()) {
+ Accessible* widget = ContainerWidget();
+ if (widget && widget->AreItemsOperable()) {
+ nsIContent* widgetElm = widget->GetContent();
+ nsIFrame* widgetFrame = widgetElm->GetPrimaryFrame();
+ if (widgetFrame && widgetFrame->IsFocusable()) {
+ focusContent = widgetElm;
+ widget->SetCurrentItem(this);
+ }
+ }
+ }
+
+ nsCOMPtr<nsIDOMElement> element(do_QueryInterface(focusContent));
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm)
+ fm->SetFocus(element, 0);
+}
+
+void
+Accessible::XULElmName(DocAccessible* aDocument,
+ nsIContent* aElm, nsString& aName)
+{
+ /**
+ * 3 main cases for XUL Controls to be labeled
+ * 1 - control contains label="foo"
+ * 2 - control has, as a child, a label element
+ * - label has either value="foo" or children
+ * 3 - non-child label contains control="controlID"
+ * - label has either value="foo" or children
+ * Once a label is found, the search is discontinued, so a control
+ * that has a label child as well as having a label external to
+ * the control that uses the control="controlID" syntax will use
+ * the child label for its Name.
+ */
+
+ // CASE #1 (via label attribute) -- great majority of the cases
+ nsCOMPtr<nsIDOMXULLabeledControlElement> labeledEl = do_QueryInterface(aElm);
+ if (labeledEl) {
+ labeledEl->GetLabel(aName);
+ } else {
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> itemEl = do_QueryInterface(aElm);
+ if (itemEl) {
+ itemEl->GetLabel(aName);
+ } else {
+ nsCOMPtr<nsIDOMXULSelectControlElement> select = do_QueryInterface(aElm);
+ // Use label if this is not a select control element which
+ // uses label attribute to indicate which option is selected
+ if (!select) {
+ nsCOMPtr<nsIDOMXULElement> xulEl(do_QueryInterface(aElm));
+ if (xulEl)
+ xulEl->GetAttribute(NS_LITERAL_STRING("label"), aName);
+ }
+ }
+ }
+
+ // CASES #2 and #3 ------ label as a child or <label control="id" ... > </label>
+ if (aName.IsEmpty()) {
+ Accessible* labelAcc = nullptr;
+ XULLabelIterator iter(aDocument, aElm);
+ while ((labelAcc = iter.Next())) {
+ nsCOMPtr<nsIDOMXULLabelElement> xulLabel =
+ do_QueryInterface(labelAcc->GetContent());
+ // Check if label's value attribute is used
+ if (xulLabel && NS_SUCCEEDED(xulLabel->GetValue(aName)) && aName.IsEmpty()) {
+ // If no value attribute, a non-empty label must contain
+ // children that define its text -- possibly using HTML
+ nsTextEquivUtils::
+ AppendTextEquivFromContent(labelAcc, labelAcc->GetContent(), &aName);
+ }
+ }
+ }
+
+ aName.CompressWhitespace();
+ if (!aName.IsEmpty())
+ return;
+
+ // Can get text from title of <toolbaritem> if we're a child of a <toolbaritem>
+ nsIContent *bindingParent = aElm->GetBindingParent();
+ nsIContent* parent =
+ bindingParent? bindingParent->GetParent() : aElm->GetParent();
+ nsAutoString ancestorTitle;
+ while (parent) {
+ if (parent->IsXULElement(nsGkAtoms::toolbaritem) &&
+ parent->GetAttr(kNameSpaceID_None, nsGkAtoms::title, ancestorTitle)) {
+ // Before returning this, check if the element itself has a tooltip:
+ if (aElm->GetAttr(kNameSpaceID_None, nsGkAtoms::tooltiptext, aName)) {
+ aName.CompressWhitespace();
+ return;
+ }
+
+ aName.Assign(ancestorTitle);
+ aName.CompressWhitespace();
+ return;
+ }
+ parent = parent->GetParent();
+ }
+}
+
+nsresult
+Accessible::HandleAccEvent(AccEvent* aEvent)
+{
+ NS_ENSURE_ARG_POINTER(aEvent);
+
+ if (IPCAccessibilityActive() && Document()) {
+ DocAccessibleChild* ipcDoc = mDoc->IPCDoc();
+ MOZ_ASSERT(ipcDoc);
+ if (ipcDoc) {
+ uint64_t id = aEvent->GetAccessible()->IsDoc() ? 0 :
+ reinterpret_cast<uintptr_t>(aEvent->GetAccessible());
+
+ switch(aEvent->GetEventType()) {
+ case nsIAccessibleEvent::EVENT_SHOW:
+ ipcDoc->ShowEvent(downcast_accEvent(aEvent));
+ break;
+
+ case nsIAccessibleEvent::EVENT_HIDE:
+ ipcDoc->SendHideEvent(id, aEvent->IsFromUserInput());
+ break;
+
+ case nsIAccessibleEvent::EVENT_REORDER:
+ // reorder events on the application acc aren't necessary to tell the parent
+ // about new top level documents.
+ if (!aEvent->GetAccessible()->IsApplication())
+ ipcDoc->SendEvent(id, aEvent->GetEventType());
+ break;
+ case nsIAccessibleEvent::EVENT_STATE_CHANGE: {
+ AccStateChangeEvent* event = downcast_accEvent(aEvent);
+ ipcDoc->SendStateChangeEvent(id, event->GetState(),
+ event->IsStateEnabled());
+ break;
+ }
+ case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
+ AccCaretMoveEvent* event = downcast_accEvent(aEvent);
+ ipcDoc->SendCaretMoveEvent(id, event->GetCaretOffset());
+ break;
+ }
+ case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
+ case nsIAccessibleEvent::EVENT_TEXT_REMOVED: {
+ AccTextChangeEvent* event = downcast_accEvent(aEvent);
+ ipcDoc->SendTextChangeEvent(id, event->ModifiedText(),
+ event->GetStartOffset(),
+ event->GetLength(),
+ event->IsTextInserted(),
+ event->IsFromUserInput());
+ break;
+ }
+ case nsIAccessibleEvent::EVENT_SELECTION:
+ case nsIAccessibleEvent::EVENT_SELECTION_ADD:
+ case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: {
+ AccSelChangeEvent* selEvent = downcast_accEvent(aEvent);
+ uint64_t widgetID = selEvent->Widget()->IsDoc() ? 0 :
+ reinterpret_cast<uintptr_t>(selEvent->Widget());
+ ipcDoc->SendSelectionEvent(id, widgetID, aEvent->GetEventType());
+ break;
+ }
+ default:
+ ipcDoc->SendEvent(id, aEvent->GetEventType());
+ }
+ }
+ }
+
+ if (nsCoreUtils::AccEventObserversExist()) {
+ nsCoreUtils::DispatchAccEvent(MakeXPCEvent(aEvent));
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<nsIPersistentProperties>
+Accessible::Attributes()
+{
+ nsCOMPtr<nsIPersistentProperties> attributes = NativeAttributes();
+ if (!HasOwnContent() || !mContent->IsElement())
+ return attributes.forget();
+
+ // 'xml-roles' attribute for landmark.
+ nsIAtom* landmark = LandmarkRole();
+ if (landmark) {
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, landmark);
+
+ } else {
+ // 'xml-roles' attribute coming from ARIA.
+ nsAutoString xmlRoles;
+ if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::role, xmlRoles))
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, xmlRoles);
+ }
+
+ // Expose object attributes from ARIA attributes.
+ nsAutoString unused;
+ aria::AttrIterator attribIter(mContent);
+ nsAutoString name, value;
+ while(attribIter.Next(name, value))
+ attributes->SetStringProperty(NS_ConvertUTF16toUTF8(name), value, unused);
+
+ if (IsARIAHidden()) {
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::hidden,
+ NS_LITERAL_STRING("true"));
+ }
+
+ // If there is no aria-live attribute then expose default value of 'live'
+ // object attribute used for ARIA role of this accessible.
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (roleMapEntry) {
+ if (roleMapEntry->Is(nsGkAtoms::searchbox)) {
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::textInputType,
+ NS_LITERAL_STRING("search"));
+ }
+
+ nsAutoString live;
+ nsAccUtils::GetAccAttr(attributes, nsGkAtoms::live, live);
+ if (live.IsEmpty()) {
+ if (nsAccUtils::GetLiveAttrValue(roleMapEntry->liveAttRule, live))
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::live, live);
+ }
+ }
+
+ return attributes.forget();
+}
+
+already_AddRefed<nsIPersistentProperties>
+Accessible::NativeAttributes()
+{
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID);
+
+ nsAutoString unused;
+
+ // We support values, so expose the string value as well, via the valuetext
+ // object attribute. We test for the value interface because we don't want
+ // to expose traditional Value() information such as URL's on links and
+ // documents, or text in an input.
+ if (HasNumericValue()) {
+ nsAutoString valuetext;
+ Value(valuetext);
+ attributes->SetStringProperty(NS_LITERAL_CSTRING("valuetext"), valuetext,
+ unused);
+ }
+
+ // Expose checkable object attribute if the accessible has checkable state
+ if (State() & states::CHECKABLE) {
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::checkable,
+ NS_LITERAL_STRING("true"));
+ }
+
+ // Expose 'explicit-name' attribute.
+ nsAutoString name;
+ if (Name(name) != eNameFromSubtree && !name.IsVoid()) {
+ attributes->SetStringProperty(NS_LITERAL_CSTRING("explicit-name"),
+ NS_LITERAL_STRING("true"), unused);
+ }
+
+ // Group attributes (level/setsize/posinset)
+ GroupPos groupPos = GroupPosition();
+ nsAccUtils::SetAccGroupAttrs(attributes, groupPos.level,
+ groupPos.setSize, groupPos.posInSet);
+
+ // If the accessible doesn't have own content (such as list item bullet or
+ // xul tree item) then don't calculate content based attributes.
+ if (!HasOwnContent())
+ return attributes.forget();
+
+ nsEventShell::GetEventAttributes(GetNode(), attributes);
+
+ // Get container-foo computed live region properties based on the closest
+ // container with the live region attribute. Inner nodes override outer nodes
+ // within the same document. The inner nodes can be used to override live
+ // region behavior on more general outer nodes. However, nodes in outer
+ // documents override nodes in inner documents: outer doc author may want to
+ // override properties on a widget they used in an iframe.
+ nsIContent* startContent = mContent;
+ while (startContent) {
+ nsIDocument* doc = startContent->GetComposedDoc();
+ if (!doc)
+ break;
+
+ nsAccUtils::SetLiveContainerAttributes(attributes, startContent,
+ doc->GetRootElement());
+
+ // Allow ARIA live region markup from outer documents to override
+ nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = doc->GetDocShell();
+ if (!docShellTreeItem)
+ break;
+
+ nsCOMPtr<nsIDocShellTreeItem> sameTypeParent;
+ docShellTreeItem->GetSameTypeParent(getter_AddRefs(sameTypeParent));
+ if (!sameTypeParent || sameTypeParent == docShellTreeItem)
+ break;
+
+ nsIDocument* parentDoc = doc->GetParentDocument();
+ if (!parentDoc)
+ break;
+
+ startContent = parentDoc->FindContentForSubDocument(doc);
+ }
+
+ if (!mContent->IsElement())
+ return attributes.forget();
+
+ nsAutoString id;
+ if (nsCoreUtils::GetID(mContent, id))
+ attributes->SetStringProperty(NS_LITERAL_CSTRING("id"), id, unused);
+
+ // Expose class because it may have useful microformat information.
+ nsAutoString _class;
+ if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::_class, _class))
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::_class, _class);
+
+ // Expose tag.
+ nsAutoString tagName;
+ mContent->NodeInfo()->GetName(tagName);
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::tag, tagName);
+
+ // Expose draggable object attribute.
+ nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(mContent);
+ if (htmlElement) {
+ bool draggable = false;
+ htmlElement->GetDraggable(&draggable);
+ if (draggable) {
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::draggable,
+ NS_LITERAL_STRING("true"));
+ }
+ }
+
+ // Don't calculate CSS-based object attributes when no frame (i.e.
+ // the accessible is unattached from the tree).
+ if (!mContent->GetPrimaryFrame())
+ return attributes.forget();
+
+ // CSS style based object attributes.
+ nsAutoString value;
+ StyleInfo styleInfo(mContent->AsElement(), mDoc->PresShell());
+
+ // Expose 'display' attribute.
+ styleInfo.Display(value);
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::display, value);
+
+ // Expose 'text-align' attribute.
+ styleInfo.TextAlign(value);
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::textAlign, value);
+
+ // Expose 'text-indent' attribute.
+ styleInfo.TextIndent(value);
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::textIndent, value);
+
+ // Expose 'margin-left' attribute.
+ styleInfo.MarginLeft(value);
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::marginLeft, value);
+
+ // Expose 'margin-right' attribute.
+ styleInfo.MarginRight(value);
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::marginRight, value);
+
+ // Expose 'margin-top' attribute.
+ styleInfo.MarginTop(value);
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::marginTop, value);
+
+ // Expose 'margin-bottom' attribute.
+ styleInfo.MarginBottom(value);
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::marginBottom, value);
+
+ return attributes.forget();
+}
+
+GroupPos
+Accessible::GroupPosition()
+{
+ GroupPos groupPos;
+ if (!HasOwnContent())
+ return groupPos;
+
+ // Get group position from ARIA attributes.
+ nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_level, &groupPos.level);
+ nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_setsize, &groupPos.setSize);
+ nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_posinset, &groupPos.posInSet);
+
+ // If ARIA is missed and the accessible is visible then calculate group
+ // position from hierarchy.
+ if (State() & states::INVISIBLE)
+ return groupPos;
+
+ // Calculate group level if ARIA is missed.
+ if (groupPos.level == 0) {
+ int32_t level = GetLevelInternal();
+ if (level != 0)
+ groupPos.level = level;
+ }
+
+ // Calculate position in group and group size if ARIA is missed.
+ if (groupPos.posInSet == 0 || groupPos.setSize == 0) {
+ int32_t posInSet = 0, setSize = 0;
+ GetPositionAndSizeInternal(&posInSet, &setSize);
+ if (posInSet != 0 && setSize != 0) {
+ if (groupPos.posInSet == 0)
+ groupPos.posInSet = posInSet;
+
+ if (groupPos.setSize == 0)
+ groupPos.setSize = setSize;
+ }
+ }
+
+ return groupPos;
+}
+
+uint64_t
+Accessible::State()
+{
+ if (IsDefunct())
+ return states::DEFUNCT;
+
+ uint64_t state = NativeState();
+ // Apply ARIA states to be sure accessible states will be overridden.
+ ApplyARIAState(&state);
+
+ // If this is an ARIA item of the selectable widget and if it's focused and
+ // not marked unselected explicitly (i.e. aria-selected="false") then expose
+ // it as selected to make ARIA widget authors life easier.
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (roleMapEntry && !(state & states::SELECTED) &&
+ !mContent->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::aria_selected,
+ nsGkAtoms::_false, eCaseMatters)) {
+ // Special case for tabs: focused tab or focus inside related tab panel
+ // implies selected state.
+ if (roleMapEntry->role == roles::PAGETAB) {
+ if (state & states::FOCUSED) {
+ state |= states::SELECTED;
+ } else {
+ // If focus is in a child of the tab panel surely the tab is selected!
+ Relation rel = RelationByType(RelationType::LABEL_FOR);
+ Accessible* relTarget = nullptr;
+ while ((relTarget = rel.Next())) {
+ if (relTarget->Role() == roles::PROPERTYPAGE &&
+ FocusMgr()->IsFocusWithin(relTarget))
+ state |= states::SELECTED;
+ }
+ }
+ } else if (state & states::FOCUSED) {
+ Accessible* container = nsAccUtils::GetSelectableContainer(this, state);
+ if (container &&
+ !nsAccUtils::HasDefinedARIAToken(container->GetContent(),
+ nsGkAtoms::aria_multiselectable)) {
+ state |= states::SELECTED;
+ }
+ }
+ }
+
+ const uint32_t kExpandCollapseStates = states::COLLAPSED | states::EXPANDED;
+ if ((state & kExpandCollapseStates) == kExpandCollapseStates) {
+ // Cannot be both expanded and collapsed -- this happens in ARIA expanded
+ // combobox because of limitation of ARIAMap.
+ // XXX: Perhaps we will be able to make this less hacky if we support
+ // extended states in ARIAMap, e.g. derive COLLAPSED from
+ // EXPANDABLE && !EXPANDED.
+ state &= ~states::COLLAPSED;
+ }
+
+ if (!(state & states::UNAVAILABLE)) {
+ state |= states::ENABLED | states::SENSITIVE;
+
+ // If the object is a current item of container widget then mark it as
+ // ACTIVE. This allows screen reader virtual buffer modes to know which
+ // descendant is the current one that would get focus if the user navigates
+ // to the container widget.
+ Accessible* widget = ContainerWidget();
+ if (widget && widget->CurrentItem() == this)
+ state |= states::ACTIVE;
+ }
+
+ if ((state & states::COLLAPSED) || (state & states::EXPANDED))
+ state |= states::EXPANDABLE;
+
+ // For some reasons DOM node may have not a frame. We tract such accessibles
+ // as invisible.
+ nsIFrame *frame = GetFrame();
+ if (!frame)
+ return state;
+
+ if (frame->StyleEffects()->mOpacity == 1.0f &&
+ !(state & states::INVISIBLE)) {
+ state |= states::OPAQUE1;
+ }
+
+ return state;
+}
+
+void
+Accessible::ApplyARIAState(uint64_t* aState) const
+{
+ if (!mContent->IsElement())
+ return;
+
+ dom::Element* element = mContent->AsElement();
+
+ // Test for universal states first
+ *aState |= aria::UniversalStatesFor(element);
+
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (roleMapEntry) {
+
+ // We only force the readonly bit off if we have a real mapping for the aria
+ // role. This preserves the ability for screen readers to use readonly
+ // (primarily on the document) as the hint for creating a virtual buffer.
+ if (roleMapEntry->role != roles::NOTHING)
+ *aState &= ~states::READONLY;
+
+ if (mContent->HasID()) {
+ // If has a role & ID and aria-activedescendant on the container, assume
+ // focusable.
+ const Accessible* ancestor = this;
+ while ((ancestor = ancestor->Parent()) && !ancestor->IsDoc()) {
+ dom::Element* el = ancestor->Elm();
+ if (el &&
+ el->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant)) {
+ *aState |= states::FOCUSABLE;
+ break;
+ }
+ }
+ }
+ }
+
+ if (*aState & states::FOCUSABLE) {
+ // Propogate aria-disabled from ancestors down to any focusable descendant.
+ const Accessible* ancestor = this;
+ while ((ancestor = ancestor->Parent()) && !ancestor->IsDoc()) {
+ dom::Element* el = ancestor->Elm();
+ if (el && el->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_disabled,
+ nsGkAtoms::_true, eCaseMatters)) {
+ *aState |= states::UNAVAILABLE;
+ break;
+ }
+ }
+ }
+
+ // special case: A native button element whose role got transformed by ARIA to a toggle button
+ // Also applies to togglable button menus, like in the Dev Tools Web Console.
+ if (IsButton() || IsMenuButton())
+ aria::MapToState(aria::eARIAPressed, element, aState);
+
+ if (!roleMapEntry)
+ return;
+
+ *aState |= roleMapEntry->state;
+
+ if (aria::MapToState(roleMapEntry->attributeMap1, element, aState) &&
+ aria::MapToState(roleMapEntry->attributeMap2, element, aState) &&
+ aria::MapToState(roleMapEntry->attributeMap3, element, aState))
+ aria::MapToState(roleMapEntry->attributeMap4, element, aState);
+
+ // ARIA gridcell inherits editable/readonly states from the grid until it's
+ // overridden.
+ if ((roleMapEntry->Is(nsGkAtoms::gridcell) ||
+ roleMapEntry->Is(nsGkAtoms::columnheader) ||
+ roleMapEntry->Is(nsGkAtoms::rowheader)) &&
+ !(*aState & (states::READONLY | states::EDITABLE))) {
+ const TableCellAccessible* cell = AsTableCell();
+ if (cell) {
+ TableAccessible* table = cell->Table();
+ if (table) {
+ Accessible* grid = table->AsAccessible();
+ uint64_t gridState = 0;
+ grid->ApplyARIAState(&gridState);
+ *aState |= (gridState & (states::READONLY | states::EDITABLE));
+ }
+ }
+ }
+}
+
+void
+Accessible::Value(nsString& aValue)
+{
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (!roleMapEntry)
+ return;
+
+ if (roleMapEntry->valueRule != eNoValue) {
+ // aria-valuenow is a number, and aria-valuetext is the optional text
+ // equivalent. For the string value, we will try the optional text
+ // equivalent first.
+ if (!mContent->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::aria_valuetext, aValue)) {
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_valuenow,
+ aValue);
+ }
+ return;
+ }
+
+ // Value of textbox is a textified subtree.
+ if (roleMapEntry->Is(nsGkAtoms::textbox)) {
+ nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue);
+ return;
+ }
+
+ // Value of combobox is a text of current or selected item.
+ if (roleMapEntry->Is(nsGkAtoms::combobox)) {
+ Accessible* option = CurrentItem();
+ if (!option) {
+ uint32_t childCount = ChildCount();
+ for (uint32_t idx = 0; idx < childCount; idx++) {
+ Accessible* child = mChildren.ElementAt(idx);
+ if (child->IsListControl()) {
+ option = child->GetSelectedItem(0);
+ break;
+ }
+ }
+ }
+
+ if (option)
+ nsTextEquivUtils::GetTextEquivFromSubtree(option, aValue);
+ }
+}
+
+double
+Accessible::MaxValue() const
+{
+ return AttrNumericValue(nsGkAtoms::aria_valuemax);
+}
+
+double
+Accessible::MinValue() const
+{
+ return AttrNumericValue(nsGkAtoms::aria_valuemin);
+}
+
+double
+Accessible::Step() const
+{
+ return UnspecifiedNaN<double>(); // no mimimum increment (step) in ARIA.
+}
+
+double
+Accessible::CurValue() const
+{
+ return AttrNumericValue(nsGkAtoms::aria_valuenow);
+}
+
+bool
+Accessible::SetCurValue(double aValue)
+{
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (!roleMapEntry || roleMapEntry->valueRule == eNoValue)
+ return false;
+
+ const uint32_t kValueCannotChange = states::READONLY | states::UNAVAILABLE;
+ if (State() & kValueCannotChange)
+ return false;
+
+ double checkValue = MinValue();
+ if (!IsNaN(checkValue) && aValue < checkValue)
+ return false;
+
+ checkValue = MaxValue();
+ if (!IsNaN(checkValue) && aValue > checkValue)
+ return false;
+
+ nsAutoString strValue;
+ strValue.AppendFloat(aValue);
+
+ return NS_SUCCEEDED(
+ mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_valuenow, strValue, true));
+}
+
+role
+Accessible::ARIATransformRole(role aRole)
+{
+ // XXX: these unfortunate exceptions don't fit into the ARIA table. This is
+ // where the accessible role depends on both the role and ARIA state.
+ if (aRole == roles::PUSHBUTTON) {
+ if (nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_pressed)) {
+ // For simplicity, any existing pressed attribute except "" or "undefined"
+ // indicates a toggle.
+ return roles::TOGGLE_BUTTON;
+ }
+
+ if (mContent->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::aria_haspopup,
+ nsGkAtoms::_true,
+ eCaseMatters)) {
+ // For button with aria-haspopup="true".
+ return roles::BUTTONMENU;
+ }
+
+ } else if (aRole == roles::LISTBOX) {
+ // A listbox inside of a combobox needs a special role because of ATK
+ // mapping to menu.
+ if (mParent && mParent->Role() == roles::COMBOBOX) {
+ return roles::COMBOBOX_LIST;
+ } else {
+ // Listbox is owned by a combobox
+ Relation rel = RelationByType(RelationType::NODE_CHILD_OF);
+ Accessible* targetAcc = nullptr;
+ while ((targetAcc = rel.Next()))
+ if (targetAcc->Role() == roles::COMBOBOX)
+ return roles::COMBOBOX_LIST;
+ }
+
+ } else if (aRole == roles::OPTION) {
+ if (mParent && mParent->Role() == roles::COMBOBOX_LIST)
+ return roles::COMBOBOX_OPTION;
+
+ } else if (aRole == roles::MENUITEM) {
+ // Menuitem has a submenu.
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_haspopup,
+ nsGkAtoms::_true, eCaseMatters)) {
+ return roles::PARENT_MENUITEM;
+ }
+ }
+
+ return aRole;
+}
+
+nsIAtom*
+Accessible::LandmarkRole() const
+{
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ return roleMapEntry && roleMapEntry->IsOfType(eLandmark) ?
+ *(roleMapEntry->roleAtom) : nullptr;
+}
+
+role
+Accessible::NativeRole()
+{
+ return roles::NOTHING;
+}
+
+uint8_t
+Accessible::ActionCount()
+{
+ return GetActionRule() == eNoAction ? 0 : 1;
+}
+
+void
+Accessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ aName.Truncate();
+
+ if (aIndex != 0)
+ return;
+
+ uint32_t actionRule = GetActionRule();
+
+ switch (actionRule) {
+ case eActivateAction:
+ aName.AssignLiteral("activate");
+ return;
+
+ case eClickAction:
+ aName.AssignLiteral("click");
+ return;
+
+ case ePressAction:
+ aName.AssignLiteral("press");
+ return;
+
+ case eCheckUncheckAction:
+ {
+ uint64_t state = State();
+ if (state & states::CHECKED)
+ aName.AssignLiteral("uncheck");
+ else if (state & states::MIXED)
+ aName.AssignLiteral("cycle");
+ else
+ aName.AssignLiteral("check");
+ return;
+ }
+
+ case eJumpAction:
+ aName.AssignLiteral("jump");
+ return;
+
+ case eOpenCloseAction:
+ if (State() & states::COLLAPSED)
+ aName.AssignLiteral("open");
+ else
+ aName.AssignLiteral("close");
+ return;
+
+ case eSelectAction:
+ aName.AssignLiteral("select");
+ return;
+
+ case eSwitchAction:
+ aName.AssignLiteral("switch");
+ return;
+
+ case eSortAction:
+ aName.AssignLiteral("sort");
+ return;
+
+ case eExpandAction:
+ if (State() & states::COLLAPSED)
+ aName.AssignLiteral("expand");
+ else
+ aName.AssignLiteral("collapse");
+ return;
+ }
+}
+
+bool
+Accessible::DoAction(uint8_t aIndex)
+{
+ if (aIndex != 0)
+ return false;
+
+ if (GetActionRule() != eNoAction) {
+ DoCommand();
+ return true;
+ }
+
+ return false;
+}
+
+nsIContent*
+Accessible::GetAtomicRegion() const
+{
+ nsIContent *loopContent = mContent;
+ nsAutoString atomic;
+ while (loopContent && !loopContent->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_atomic, atomic))
+ loopContent = loopContent->GetParent();
+
+ return atomic.EqualsLiteral("true") ? loopContent : nullptr;
+}
+
+Relation
+Accessible::RelationByType(RelationType aType)
+{
+ if (!HasOwnContent())
+ return Relation();
+
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+
+ // Relationships are defined on the same content node that the role would be
+ // defined on.
+ switch (aType) {
+ case RelationType::LABELLED_BY: {
+ Relation rel(new IDRefsIterator(mDoc, mContent,
+ nsGkAtoms::aria_labelledby));
+ if (mContent->IsHTMLElement()) {
+ rel.AppendIter(new HTMLLabelIterator(Document(), this));
+ } else if (mContent->IsXULElement()) {
+ rel.AppendIter(new XULLabelIterator(Document(), mContent));
+ }
+
+ return rel;
+ }
+
+ case RelationType::LABEL_FOR: {
+ Relation rel(new RelatedAccIterator(Document(), mContent,
+ nsGkAtoms::aria_labelledby));
+ if (mContent->IsXULElement(nsGkAtoms::label))
+ rel.AppendIter(new IDRefsIterator(mDoc, mContent, nsGkAtoms::control));
+
+ return rel;
+ }
+
+ case RelationType::DESCRIBED_BY: {
+ Relation rel(new IDRefsIterator(mDoc, mContent,
+ nsGkAtoms::aria_describedby));
+ if (mContent->IsXULElement())
+ rel.AppendIter(new XULDescriptionIterator(Document(), mContent));
+
+ return rel;
+ }
+
+ case RelationType::DESCRIPTION_FOR: {
+ Relation rel(new RelatedAccIterator(Document(), mContent,
+ nsGkAtoms::aria_describedby));
+
+ // This affectively adds an optional control attribute to xul:description,
+ // which only affects accessibility, by allowing the description to be
+ // tied to a control.
+ if (mContent->IsXULElement(nsGkAtoms::description))
+ rel.AppendIter(new IDRefsIterator(mDoc, mContent,
+ nsGkAtoms::control));
+
+ return rel;
+ }
+
+ case RelationType::NODE_CHILD_OF: {
+ Relation rel;
+ // This is an ARIA tree or treegrid that doesn't use owns, so we need to
+ // get the parent the hard way.
+ if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
+ roleMapEntry->role == roles::LISTITEM ||
+ roleMapEntry->role == roles::ROW)) {
+ rel.AppendTarget(GetGroupInfo()->ConceptualParent());
+ }
+
+ // If accessible is in its own Window, or is the root of a document,
+ // then we should provide NODE_CHILD_OF relation so that MSAA clients
+ // can easily get to true parent instead of getting to oleacc's
+ // ROLE_WINDOW accessible which will prevent us from going up further
+ // (because it is system generated and has no idea about the hierarchy
+ // above it).
+ nsIFrame *frame = GetFrame();
+ if (frame) {
+ nsView *view = frame->GetView();
+ if (view) {
+ nsIScrollableFrame *scrollFrame = do_QueryFrame(frame);
+ if (scrollFrame || view->GetWidget() || !frame->GetParent())
+ rel.AppendTarget(Parent());
+ }
+ }
+
+ return rel;
+ }
+
+ case RelationType::NODE_PARENT_OF: {
+ // ARIA tree or treegrid can do the hierarchy by @aria-level, ARIA trees
+ // also can be organized by groups.
+ if (roleMapEntry &&
+ (roleMapEntry->role == roles::OUTLINEITEM ||
+ roleMapEntry->role == roles::LISTITEM ||
+ roleMapEntry->role == roles::ROW ||
+ roleMapEntry->role == roles::OUTLINE ||
+ roleMapEntry->role == roles::LIST ||
+ roleMapEntry->role == roles::TREE_TABLE)) {
+ return Relation(new ItemIterator(this));
+ }
+
+ return Relation();
+ }
+
+ case RelationType::CONTROLLED_BY:
+ return Relation(new RelatedAccIterator(Document(), mContent,
+ nsGkAtoms::aria_controls));
+
+ case RelationType::CONTROLLER_FOR: {
+ Relation rel(new IDRefsIterator(mDoc, mContent,
+ nsGkAtoms::aria_controls));
+ rel.AppendIter(new HTMLOutputIterator(Document(), mContent));
+ return rel;
+ }
+
+ case RelationType::FLOWS_TO:
+ return Relation(new IDRefsIterator(mDoc, mContent,
+ nsGkAtoms::aria_flowto));
+
+ case RelationType::FLOWS_FROM:
+ return Relation(new RelatedAccIterator(Document(), mContent,
+ nsGkAtoms::aria_flowto));
+
+ case RelationType::MEMBER_OF:
+ return Relation(mDoc, GetAtomicRegion());
+
+ case RelationType::SUBWINDOW_OF:
+ case RelationType::EMBEDS:
+ case RelationType::EMBEDDED_BY:
+ case RelationType::POPUP_FOR:
+ case RelationType::PARENT_WINDOW_OF:
+ return Relation();
+
+ case RelationType::DEFAULT_BUTTON: {
+ if (mContent->IsHTMLElement()) {
+ // HTML form controls implements nsIFormControl interface.
+ nsCOMPtr<nsIFormControl> control(do_QueryInterface(mContent));
+ if (control) {
+ nsCOMPtr<nsIForm> form(do_QueryInterface(control->GetFormElement()));
+ if (form) {
+ nsCOMPtr<nsIContent> formContent =
+ do_QueryInterface(form->GetDefaultSubmitElement());
+ return Relation(mDoc, formContent);
+ }
+ }
+ } else {
+ // In XUL, use first <button default="true" .../> in the document
+ nsCOMPtr<nsIDOMXULDocument> xulDoc =
+ do_QueryInterface(mContent->OwnerDoc());
+ nsCOMPtr<nsIDOMXULButtonElement> buttonEl;
+ if (xulDoc) {
+ nsCOMPtr<nsIDOMNodeList> possibleDefaultButtons;
+ xulDoc->GetElementsByAttribute(NS_LITERAL_STRING("default"),
+ NS_LITERAL_STRING("true"),
+ getter_AddRefs(possibleDefaultButtons));
+ if (possibleDefaultButtons) {
+ uint32_t length;
+ possibleDefaultButtons->GetLength(&length);
+ nsCOMPtr<nsIDOMNode> possibleButton;
+ // Check for button in list of default="true" elements
+ for (uint32_t count = 0; count < length && !buttonEl; count ++) {
+ possibleDefaultButtons->Item(count, getter_AddRefs(possibleButton));
+ buttonEl = do_QueryInterface(possibleButton);
+ }
+ }
+ if (!buttonEl) { // Check for anonymous accept button in <dialog>
+ dom::Element* rootElm = mContent->OwnerDoc()->GetRootElement();
+ if (rootElm) {
+ nsIContent* possibleButtonEl = rootElm->OwnerDoc()->
+ GetAnonymousElementByAttribute(rootElm, nsGkAtoms::_default,
+ NS_LITERAL_STRING("true"));
+ buttonEl = do_QueryInterface(possibleButtonEl);
+ }
+ }
+ nsCOMPtr<nsIContent> relatedContent(do_QueryInterface(buttonEl));
+ return Relation(mDoc, relatedContent);
+ }
+ }
+ return Relation();
+ }
+
+ case RelationType::CONTAINING_DOCUMENT:
+ return Relation(mDoc);
+
+ case RelationType::CONTAINING_TAB_PANE: {
+ nsCOMPtr<nsIDocShell> docShell =
+ nsCoreUtils::GetDocShellFor(GetNode());
+ if (docShell) {
+ // Walk up the parent chain without crossing the boundary at which item
+ // types change, preventing us from walking up out of tab content.
+ nsCOMPtr<nsIDocShellTreeItem> root;
+ docShell->GetSameTypeRootTreeItem(getter_AddRefs(root));
+ if (root) {
+ // If the item type is typeContent, we assume we are in browser tab
+ // content. Note, this includes content such as about:addons,
+ // for consistency.
+ if (root->ItemType() == nsIDocShellTreeItem::typeContent) {
+ return Relation(nsAccUtils::GetDocAccessibleFor(root));
+ }
+ }
+ }
+ return Relation();
+ }
+
+ case RelationType::CONTAINING_APPLICATION:
+ return Relation(ApplicationAcc());
+
+ case RelationType::DETAILS:
+ return Relation(new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_details));
+
+ case RelationType::DETAILS_FOR:
+ return Relation(new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_details));
+
+ case RelationType::ERRORMSG:
+ return Relation(new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_errormessage));
+
+ case RelationType::ERRORMSG_FOR:
+ return Relation(new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_errormessage));
+
+ default:
+ return Relation();
+ }
+}
+
+void
+Accessible::GetNativeInterface(void** aNativeAccessible)
+{
+}
+
+void
+Accessible::DoCommand(nsIContent *aContent, uint32_t aActionIndex)
+{
+ class Runnable final : public mozilla::Runnable
+ {
+ public:
+ Runnable(Accessible* aAcc, nsIContent* aContent, uint32_t aIdx) :
+ mAcc(aAcc), mContent(aContent), mIdx(aIdx) { }
+
+ NS_IMETHOD Run() override
+ {
+ if (mAcc)
+ mAcc->DispatchClickEvent(mContent, mIdx);
+
+ return NS_OK;
+ }
+
+ void Revoke()
+ {
+ mAcc = nullptr;
+ mContent = nullptr;
+ }
+
+ private:
+ RefPtr<Accessible> mAcc;
+ nsCOMPtr<nsIContent> mContent;
+ uint32_t mIdx;
+ };
+
+ nsIContent* content = aContent ? aContent : mContent.get();
+ nsCOMPtr<nsIRunnable> runnable = new Runnable(this, content, aActionIndex);
+ NS_DispatchToMainThread(runnable);
+}
+
+void
+Accessible::DispatchClickEvent(nsIContent *aContent, uint32_t aActionIndex)
+{
+ if (IsDefunct())
+ return;
+
+ nsCOMPtr<nsIPresShell> presShell = mDoc->PresShell();
+
+ // Scroll into view.
+ presShell->ScrollContentIntoView(aContent,
+ nsIPresShell::ScrollAxis(),
+ nsIPresShell::ScrollAxis(),
+ nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
+
+ nsWeakFrame frame = aContent->GetPrimaryFrame();
+ if (!frame)
+ return;
+
+ // Compute x and y coordinates.
+ nsPoint point;
+ nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget(point);
+ if (!widget)
+ return;
+
+ nsSize size = frame->GetSize();
+
+ RefPtr<nsPresContext> presContext = presShell->GetPresContext();
+ int32_t x = presContext->AppUnitsToDevPixels(point.x + size.width / 2);
+ int32_t y = presContext->AppUnitsToDevPixels(point.y + size.height / 2);
+
+ // Simulate a touch interaction by dispatching touch events with mouse events.
+ nsCoreUtils::DispatchTouchEvent(eTouchStart, x, y, aContent, frame,
+ presShell, widget);
+ nsCoreUtils::DispatchMouseEvent(eMouseDown, x, y, aContent, frame,
+ presShell, widget);
+ nsCoreUtils::DispatchTouchEvent(eTouchEnd, x, y, aContent, frame,
+ presShell, widget);
+ nsCoreUtils::DispatchMouseEvent(eMouseUp, x, y, aContent, frame,
+ presShell, widget);
+}
+
+void
+Accessible::ScrollToPoint(uint32_t aCoordinateType, int32_t aX, int32_t aY)
+{
+ nsIFrame* frame = GetFrame();
+ if (!frame)
+ return;
+
+ nsIntPoint coords =
+ nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType, this);
+
+ nsIFrame* parentFrame = frame;
+ while ((parentFrame = parentFrame->GetParent()))
+ nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords);
+}
+
+void
+Accessible::AppendTextTo(nsAString& aText, uint32_t aStartOffset,
+ uint32_t aLength)
+{
+ // Return text representation of non-text accessible within hypertext
+ // accessible. Text accessible overrides this method to return enclosed text.
+ if (aStartOffset != 0 || aLength == 0)
+ return;
+
+ nsIFrame *frame = GetFrame();
+ if (!frame)
+ return;
+
+ NS_ASSERTION(mParent,
+ "Called on accessible unbound from tree. Result can be wrong.");
+
+ if (frame->GetType() == nsGkAtoms::brFrame) {
+ aText += kForcedNewLineChar;
+ } else if (mParent && nsAccUtils::MustPrune(mParent)) {
+ // Expose the embedded object accessible as imaginary embedded object
+ // character if its parent hypertext accessible doesn't expose children to
+ // AT.
+ aText += kImaginaryEmbeddedObjectChar;
+ } else {
+ aText += kEmbeddedObjectChar;
+ }
+}
+
+void
+Accessible::Shutdown()
+{
+ // Mark the accessible as defunct, invalidate the child count and pointers to
+ // other accessibles, also make sure none of its children point to this parent
+ mStateFlags |= eIsDefunct;
+
+ int32_t childCount = mChildren.Length();
+ for (int32_t childIdx = 0; childIdx < childCount; childIdx++) {
+ mChildren.ElementAt(childIdx)->UnbindFromParent();
+ }
+ mChildren.Clear();
+
+ mEmbeddedObjCollector = nullptr;
+
+ if (mParent)
+ mParent->RemoveChild(this);
+
+ mContent = nullptr;
+ mDoc = nullptr;
+ if (SelectionMgr() && SelectionMgr()->AccessibleWithCaret(nullptr) == this)
+ SelectionMgr()->ResetCaretOffset();
+}
+
+// Accessible protected
+void
+Accessible::ARIAName(nsString& aName)
+{
+ // aria-labelledby now takes precedence over aria-label
+ nsresult rv = nsTextEquivUtils::
+ GetTextEquivFromIDRefs(this, nsGkAtoms::aria_labelledby, aName);
+ if (NS_SUCCEEDED(rv)) {
+ aName.CompressWhitespace();
+ }
+
+ if (aName.IsEmpty() &&
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_label, aName)) {
+ aName.CompressWhitespace();
+ }
+}
+
+// Accessible protected
+ENameValueFlag
+Accessible::NativeName(nsString& aName)
+{
+ if (mContent->IsHTMLElement()) {
+ Accessible* label = nullptr;
+ HTMLLabelIterator iter(Document(), this);
+ while ((label = iter.Next())) {
+ nsTextEquivUtils::AppendTextEquivFromContent(this, label->GetContent(),
+ &aName);
+ aName.CompressWhitespace();
+ }
+
+ if (!aName.IsEmpty())
+ return eNameOK;
+
+ nsTextEquivUtils::GetNameFromSubtree(this, aName);
+ return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
+ }
+
+ if (mContent->IsXULElement()) {
+ XULElmName(mDoc, mContent, aName);
+ if (!aName.IsEmpty())
+ return eNameOK;
+
+ nsTextEquivUtils::GetNameFromSubtree(this, aName);
+ return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
+ }
+
+ if (mContent->IsSVGElement()) {
+ // If user agents need to choose among multiple ‘desc’ or ‘title’ elements
+ // for processing, the user agent shall choose the first one.
+ for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
+ childElm = childElm->GetNextSibling()) {
+ if (childElm->IsSVGElement(nsGkAtoms::title)) {
+ nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aName);
+ return eNameOK;
+ }
+ }
+ }
+
+ return eNameOK;
+}
+
+// Accessible protected
+void
+Accessible::NativeDescription(nsString& aDescription)
+{
+ bool isXUL = mContent->IsXULElement();
+ if (isXUL) {
+ // Try XUL <description control="[id]">description text</description>
+ XULDescriptionIterator iter(Document(), mContent);
+ Accessible* descr = nullptr;
+ while ((descr = iter.Next())) {
+ nsTextEquivUtils::AppendTextEquivFromContent(this, descr->GetContent(),
+ &aDescription);
+ }
+ }
+}
+
+// Accessible protected
+void
+Accessible::BindToParent(Accessible* aParent, uint32_t aIndexInParent)
+{
+ MOZ_ASSERT(aParent, "This method isn't used to set null parent");
+ MOZ_ASSERT(!mParent, "The child was expected to be moved");
+
+#ifdef A11Y_LOG
+ if (mParent) {
+ logging::TreeInfo("BindToParent: stealing accessible", 0,
+ "old parent", mParent,
+ "new parent", aParent,
+ "child", this, nullptr);
+ }
+#endif
+
+ mParent = aParent;
+ mIndexInParent = aIndexInParent;
+
+ // Note: this is currently only used for richlistitems and their children.
+ if (mParent->HasNameDependentParent() || mParent->IsXULListItem())
+ mContextFlags |= eHasNameDependentParent;
+ else
+ mContextFlags &= ~eHasNameDependentParent;
+
+ if (mParent->IsARIAHidden() || aria::HasDefinedARIAHidden(mContent))
+ SetARIAHidden(true);
+
+ mContextFlags |=
+ static_cast<uint32_t>((mParent->IsAlert() ||
+ mParent->IsInsideAlert())) & eInsideAlert;
+}
+
+// Accessible protected
+void
+Accessible::UnbindFromParent()
+{
+ mParent = nullptr;
+ mIndexInParent = -1;
+ mInt.mIndexOfEmbeddedChild = -1;
+ if (IsProxy())
+ MOZ_CRASH("this should never be called on proxy wrappers");
+
+ delete mBits.groupInfo;
+ mBits.groupInfo = nullptr;
+ mContextFlags &= ~eHasNameDependentParent & ~eInsideAlert;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible public methods
+
+RootAccessible*
+Accessible::RootAccessible() const
+{
+ nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(GetNode());
+ NS_ASSERTION(docShell, "No docshell for mContent");
+ if (!docShell) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> root;
+ docShell->GetRootTreeItem(getter_AddRefs(root));
+ NS_ASSERTION(root, "No root content tree item");
+ if (!root) {
+ return nullptr;
+ }
+
+ DocAccessible* docAcc = nsAccUtils::GetDocAccessibleFor(root);
+ return docAcc ? docAcc->AsRoot() : nullptr;
+}
+
+nsIFrame*
+Accessible::GetFrame() const
+{
+ return mContent ? mContent->GetPrimaryFrame() : nullptr;
+}
+
+nsINode*
+Accessible::GetNode() const
+{
+ return mContent;
+}
+
+void
+Accessible::Language(nsAString& aLanguage)
+{
+ aLanguage.Truncate();
+
+ if (!mDoc)
+ return;
+
+ nsCoreUtils::GetLanguageFor(mContent, nullptr, aLanguage);
+ if (aLanguage.IsEmpty()) { // Nothing found, so use document's language
+ mDoc->DocumentNode()->GetHeaderData(nsGkAtoms::headerContentLanguage,
+ aLanguage);
+ }
+}
+
+bool
+Accessible::InsertChildAt(uint32_t aIndex, Accessible* aChild)
+{
+ if (!aChild)
+ return false;
+
+ if (aIndex == mChildren.Length()) {
+ if (!mChildren.AppendElement(aChild))
+ return false;
+
+ } else {
+ if (!mChildren.InsertElementAt(aIndex, aChild))
+ return false;
+
+ MOZ_ASSERT(mStateFlags & eKidsMutating, "Illicit children change");
+
+ for (uint32_t idx = aIndex + 1; idx < mChildren.Length(); idx++) {
+ mChildren[idx]->mIndexInParent = idx;
+ }
+ }
+
+ if (aChild->IsText()) {
+ mStateFlags |= eHasTextKids;
+ }
+
+ aChild->BindToParent(this, aIndex);
+ return true;
+}
+
+bool
+Accessible::RemoveChild(Accessible* aChild)
+{
+ if (!aChild)
+ return false;
+
+ if (aChild->mParent != this || aChild->mIndexInParent == -1)
+ return false;
+
+ MOZ_ASSERT((mStateFlags & eKidsMutating) || aChild->IsDefunct() || aChild->IsDoc(),
+ "Illicit children change");
+
+ int32_t index = static_cast<uint32_t>(aChild->mIndexInParent);
+ if (mChildren.SafeElementAt(index) != aChild) {
+ MOZ_ASSERT_UNREACHABLE("A wrong child index");
+ index = mChildren.IndexOf(aChild);
+ if (index == -1) {
+ MOZ_ASSERT_UNREACHABLE("No child was found");
+ return false;
+ }
+ }
+
+ aChild->UnbindFromParent();
+ mChildren.RemoveElementAt(index);
+
+ for (uint32_t idx = index; idx < mChildren.Length(); idx++) {
+ mChildren[idx]->mIndexInParent = idx;
+ }
+
+ return true;
+}
+
+void
+Accessible::MoveChild(uint32_t aNewIndex, Accessible* aChild)
+{
+ MOZ_ASSERT(aChild, "No child was given");
+ MOZ_ASSERT(aChild->mParent == this, "A child from different subtree was given");
+ MOZ_ASSERT(aChild->mIndexInParent != -1, "Unbound child was given");
+ MOZ_ASSERT(static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex,
+ "No move, same index");
+ MOZ_ASSERT(aNewIndex <= mChildren.Length(), "Wrong new index was given");
+
+ RefPtr<AccHideEvent> hideEvent = new AccHideEvent(aChild, false);
+ if (mDoc->Controller()->QueueMutationEvent(hideEvent)) {
+ aChild->SetHideEventTarget(true);
+ }
+
+ mEmbeddedObjCollector = nullptr;
+ mChildren.RemoveElementAt(aChild->mIndexInParent);
+
+ uint32_t startIdx = aNewIndex, endIdx = aChild->mIndexInParent;
+
+ // If the child is moved after its current position.
+ if (static_cast<uint32_t>(aChild->mIndexInParent) < aNewIndex) {
+ startIdx = aChild->mIndexInParent;
+ if (aNewIndex == mChildren.Length() + 1) {
+ // The child is moved to the end.
+ mChildren.AppendElement(aChild);
+ endIdx = mChildren.Length() - 1;
+ }
+ else {
+ mChildren.InsertElementAt(aNewIndex - 1, aChild);
+ endIdx = aNewIndex;
+ }
+ }
+ else {
+ // The child is moved prior its current position.
+ mChildren.InsertElementAt(aNewIndex, aChild);
+ }
+
+ for (uint32_t idx = startIdx; idx <= endIdx; idx++) {
+ mChildren[idx]->mIndexInParent = idx;
+ mChildren[idx]->mStateFlags |= eGroupInfoDirty;
+ mChildren[idx]->mInt.mIndexOfEmbeddedChild = -1;
+ }
+
+ RefPtr<AccShowEvent> showEvent = new AccShowEvent(aChild);
+ DebugOnly<bool> added = mDoc->Controller()->QueueMutationEvent(showEvent);
+ MOZ_ASSERT(added);
+ aChild->SetShowEventTarget(true);
+}
+
+Accessible*
+Accessible::GetChildAt(uint32_t aIndex) const
+{
+ Accessible* child = mChildren.SafeElementAt(aIndex, nullptr);
+ if (!child)
+ return nullptr;
+
+#ifdef DEBUG
+ Accessible* realParent = child->mParent;
+ NS_ASSERTION(!realParent || realParent == this,
+ "Two accessibles have the same first child accessible!");
+#endif
+
+ return child;
+}
+
+uint32_t
+Accessible::ChildCount() const
+{
+ return mChildren.Length();
+}
+
+int32_t
+Accessible::IndexInParent() const
+{
+ return mIndexInParent;
+}
+
+uint32_t
+Accessible::EmbeddedChildCount()
+{
+ if (mStateFlags & eHasTextKids) {
+ if (!mEmbeddedObjCollector)
+ mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
+ return mEmbeddedObjCollector->Count();
+ }
+
+ return ChildCount();
+}
+
+Accessible*
+Accessible::GetEmbeddedChildAt(uint32_t aIndex)
+{
+ if (mStateFlags & eHasTextKids) {
+ if (!mEmbeddedObjCollector)
+ mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
+ return mEmbeddedObjCollector.get() ?
+ mEmbeddedObjCollector->GetAccessibleAt(aIndex) : nullptr;
+ }
+
+ return GetChildAt(aIndex);
+}
+
+int32_t
+Accessible::GetIndexOfEmbeddedChild(Accessible* aChild)
+{
+ if (mStateFlags & eHasTextKids) {
+ if (!mEmbeddedObjCollector)
+ mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
+ return mEmbeddedObjCollector.get() ?
+ mEmbeddedObjCollector->GetIndexAt(aChild) : -1;
+ }
+
+ return GetIndexOf(aChild);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HyperLinkAccessible methods
+
+bool
+Accessible::IsLink()
+{
+ // Every embedded accessible within hypertext accessible implements
+ // hyperlink interface.
+ return mParent && mParent->IsHyperText() && !IsText();
+}
+
+uint32_t
+Accessible::StartOffset()
+{
+ NS_PRECONDITION(IsLink(), "StartOffset is called not on hyper link!");
+
+ HyperTextAccessible* hyperText = mParent ? mParent->AsHyperText() : nullptr;
+ return hyperText ? hyperText->GetChildOffset(this) : 0;
+}
+
+uint32_t
+Accessible::EndOffset()
+{
+ NS_PRECONDITION(IsLink(), "EndOffset is called on not hyper link!");
+
+ HyperTextAccessible* hyperText = mParent ? mParent->AsHyperText() : nullptr;
+ return hyperText ? (hyperText->GetChildOffset(this) + 1) : 0;
+}
+
+uint32_t
+Accessible::AnchorCount()
+{
+ NS_PRECONDITION(IsLink(), "AnchorCount is called on not hyper link!");
+ return 1;
+}
+
+Accessible*
+Accessible::AnchorAt(uint32_t aAnchorIndex)
+{
+ NS_PRECONDITION(IsLink(), "GetAnchor is called on not hyper link!");
+ return aAnchorIndex == 0 ? this : nullptr;
+}
+
+already_AddRefed<nsIURI>
+Accessible::AnchorURIAt(uint32_t aAnchorIndex)
+{
+ NS_PRECONDITION(IsLink(), "AnchorURIAt is called on not hyper link!");
+ return nullptr;
+}
+
+void
+Accessible::ToTextPoint(HyperTextAccessible** aContainer, int32_t* aOffset,
+ bool aIsBefore) const
+{
+ if (IsHyperText()) {
+ *aContainer = const_cast<Accessible*>(this)->AsHyperText();
+ *aOffset = aIsBefore ? 0 : (*aContainer)->CharacterCount();
+ return;
+ }
+
+ const Accessible* child = nullptr;
+ const Accessible* parent = this;
+ do {
+ child = parent;
+ parent = parent->Parent();
+ } while (parent && !parent->IsHyperText());
+
+ if (parent) {
+ *aContainer = const_cast<Accessible*>(parent)->AsHyperText();
+ *aOffset = (*aContainer)->GetChildOffset(
+ child->IndexInParent() + static_cast<int32_t>(!aIsBefore));
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// SelectAccessible
+
+void
+Accessible::SelectedItems(nsTArray<Accessible*>* aItems)
+{
+ AccIterator iter(this, filters::GetSelected);
+ Accessible* selected = nullptr;
+ while ((selected = iter.Next()))
+ aItems->AppendElement(selected);
+}
+
+uint32_t
+Accessible::SelectedItemCount()
+{
+ uint32_t count = 0;
+ AccIterator iter(this, filters::GetSelected);
+ Accessible* selected = nullptr;
+ while ((selected = iter.Next()))
+ ++count;
+
+ return count;
+}
+
+Accessible*
+Accessible::GetSelectedItem(uint32_t aIndex)
+{
+ AccIterator iter(this, filters::GetSelected);
+ Accessible* selected = nullptr;
+
+ uint32_t index = 0;
+ while ((selected = iter.Next()) && index < aIndex)
+ index++;
+
+ return selected;
+}
+
+bool
+Accessible::IsItemSelected(uint32_t aIndex)
+{
+ uint32_t index = 0;
+ AccIterator iter(this, filters::GetSelectable);
+ Accessible* selected = nullptr;
+ while ((selected = iter.Next()) && index < aIndex)
+ index++;
+
+ return selected &&
+ selected->State() & states::SELECTED;
+}
+
+bool
+Accessible::AddItemToSelection(uint32_t aIndex)
+{
+ uint32_t index = 0;
+ AccIterator iter(this, filters::GetSelectable);
+ Accessible* selected = nullptr;
+ while ((selected = iter.Next()) && index < aIndex)
+ index++;
+
+ if (selected)
+ selected->SetSelected(true);
+
+ return static_cast<bool>(selected);
+}
+
+bool
+Accessible::RemoveItemFromSelection(uint32_t aIndex)
+{
+ uint32_t index = 0;
+ AccIterator iter(this, filters::GetSelectable);
+ Accessible* selected = nullptr;
+ while ((selected = iter.Next()) && index < aIndex)
+ index++;
+
+ if (selected)
+ selected->SetSelected(false);
+
+ return static_cast<bool>(selected);
+}
+
+bool
+Accessible::SelectAll()
+{
+ bool success = false;
+ Accessible* selectable = nullptr;
+
+ AccIterator iter(this, filters::GetSelectable);
+ while((selectable = iter.Next())) {
+ success = true;
+ selectable->SetSelected(true);
+ }
+ return success;
+}
+
+bool
+Accessible::UnselectAll()
+{
+ bool success = false;
+ Accessible* selected = nullptr;
+
+ AccIterator iter(this, filters::GetSelected);
+ while ((selected = iter.Next())) {
+ success = true;
+ selected->SetSelected(false);
+ }
+ return success;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Widgets
+
+bool
+Accessible::IsWidget() const
+{
+ return false;
+}
+
+bool
+Accessible::IsActiveWidget() const
+{
+ if (FocusMgr()->HasDOMFocus(mContent))
+ return true;
+
+ // If text entry of combobox widget has a focus then the combobox widget is
+ // active.
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (roleMapEntry && roleMapEntry->Is(nsGkAtoms::combobox)) {
+ uint32_t childCount = ChildCount();
+ for (uint32_t idx = 0; idx < childCount; idx++) {
+ Accessible* child = mChildren.ElementAt(idx);
+ if (child->Role() == roles::ENTRY)
+ return FocusMgr()->HasDOMFocus(child->GetContent());
+ }
+ }
+
+ return false;
+}
+
+bool
+Accessible::AreItemsOperable() const
+{
+ return HasOwnContent() &&
+ mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant);
+}
+
+Accessible*
+Accessible::CurrentItem()
+{
+ // Check for aria-activedescendant, which changes which element has focus.
+ // For activedescendant, the ARIA spec does not require that the user agent
+ // checks whether pointed node is actually a DOM descendant of the element
+ // with the aria-activedescendant attribute.
+ nsAutoString id;
+ if (HasOwnContent() &&
+ mContent->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::aria_activedescendant, id)) {
+ nsIDocument* DOMDoc = mContent->OwnerDoc();
+ dom::Element* activeDescendantElm = DOMDoc->GetElementById(id);
+ if (activeDescendantElm) {
+ DocAccessible* document = Document();
+ if (document)
+ return document->GetAccessible(activeDescendantElm);
+ }
+ }
+ return nullptr;
+}
+
+void
+Accessible::SetCurrentItem(Accessible* aItem)
+{
+ nsIAtom* id = aItem->GetContent()->GetID();
+ if (id) {
+ nsAutoString idStr;
+ id->ToString(idStr);
+ mContent->SetAttr(kNameSpaceID_None,
+ nsGkAtoms::aria_activedescendant, idStr, true);
+ }
+}
+
+Accessible*
+Accessible::ContainerWidget() const
+{
+ if (HasARIARole() && mContent->HasID()) {
+ for (Accessible* parent = Parent(); parent; parent = parent->Parent()) {
+ nsIContent* parentContent = parent->GetContent();
+ if (parentContent &&
+ parentContent->HasAttr(kNameSpaceID_None,
+ nsGkAtoms::aria_activedescendant)) {
+ return parent;
+ }
+
+ // Don't cross DOM document boundaries.
+ if (parent->IsDoc())
+ break;
+ }
+ }
+ return nullptr;
+}
+
+void
+Accessible::SetARIAHidden(bool aIsDefined)
+{
+ if (aIsDefined)
+ mContextFlags |= eARIAHidden;
+ else
+ mContextFlags &= ~eARIAHidden;
+
+ uint32_t length = mChildren.Length();
+ for (uint32_t i = 0; i < length; i++) {
+ mChildren[i]->SetARIAHidden(aIsDefined);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible protected methods
+
+void
+Accessible::LastRelease()
+{
+ // First cleanup if needed...
+ if (mDoc) {
+ Shutdown();
+ NS_ASSERTION(!mDoc,
+ "A Shutdown() impl forgot to call its parent's Shutdown?");
+ }
+ // ... then die.
+ delete this;
+}
+
+Accessible*
+Accessible::GetSiblingAtOffset(int32_t aOffset, nsresult* aError) const
+{
+ if (!mParent || mIndexInParent == -1) {
+ if (aError)
+ *aError = NS_ERROR_UNEXPECTED;
+
+ return nullptr;
+ }
+
+ if (aError &&
+ mIndexInParent + aOffset >= static_cast<int32_t>(mParent->ChildCount())) {
+ *aError = NS_OK; // fail peacefully
+ return nullptr;
+ }
+
+ Accessible* child = mParent->GetChildAt(mIndexInParent + aOffset);
+ if (aError && !child)
+ *aError = NS_ERROR_UNEXPECTED;
+
+ return child;
+}
+
+double
+Accessible::AttrNumericValue(nsIAtom* aAttr) const
+{
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (!roleMapEntry || roleMapEntry->valueRule == eNoValue)
+ return UnspecifiedNaN<double>();
+
+ nsAutoString attrValue;
+ if (!mContent->GetAttr(kNameSpaceID_None, aAttr, attrValue))
+ return UnspecifiedNaN<double>();
+
+ nsresult error = NS_OK;
+ double value = attrValue.ToDouble(&error);
+ return NS_FAILED(error) ? UnspecifiedNaN<double>() : value;
+}
+
+uint32_t
+Accessible::GetActionRule() const
+{
+ if (!HasOwnContent() || (InteractiveState() & states::UNAVAILABLE))
+ return eNoAction;
+
+ // Return "click" action on elements that have an attached popup menu.
+ if (mContent->IsXULElement())
+ if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::popup))
+ return eClickAction;
+
+ // Has registered 'click' event handler.
+ bool isOnclick = nsCoreUtils::HasClickListener(mContent);
+
+ if (isOnclick)
+ return eClickAction;
+
+ // Get an action based on ARIA role.
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (roleMapEntry &&
+ roleMapEntry->actionRule != eNoAction)
+ return roleMapEntry->actionRule;
+
+ // Get an action based on ARIA attribute.
+ if (nsAccUtils::HasDefinedARIAToken(mContent,
+ nsGkAtoms::aria_expanded))
+ return eExpandAction;
+
+ return eNoAction;
+}
+
+AccGroupInfo*
+Accessible::GetGroupInfo()
+{
+ if (IsProxy())
+ MOZ_CRASH("This should never be called on proxy wrappers");
+
+ if (mBits.groupInfo){
+ if (HasDirtyGroupInfo()) {
+ mBits.groupInfo->Update();
+ mStateFlags &= ~eGroupInfoDirty;
+ }
+
+ return mBits.groupInfo;
+ }
+
+ mBits.groupInfo = AccGroupInfo::CreateGroupInfo(this);
+ return mBits.groupInfo;
+}
+
+void
+Accessible::GetPositionAndSizeInternal(int32_t *aPosInSet, int32_t *aSetSize)
+{
+ AccGroupInfo* groupInfo = GetGroupInfo();
+ if (groupInfo) {
+ *aPosInSet = groupInfo->PosInSet();
+ *aSetSize = groupInfo->SetSize();
+ }
+}
+
+int32_t
+Accessible::GetLevelInternal()
+{
+ int32_t level = nsAccUtils::GetDefaultLevel(this);
+
+ if (!IsBoundToParent())
+ return level;
+
+ roles::Role role = Role();
+ if (role == roles::OUTLINEITEM) {
+ // Always expose 'level' attribute for 'outlineitem' accessible. The number
+ // of nested 'grouping' accessibles containing 'outlineitem' accessible is
+ // its level.
+ level = 1;
+
+ Accessible* parent = this;
+ while ((parent = parent->Parent())) {
+ roles::Role parentRole = parent->Role();
+
+ if (parentRole == roles::OUTLINE)
+ break;
+ if (parentRole == roles::GROUPING)
+ ++ level;
+
+ }
+
+ } else if (role == roles::LISTITEM) {
+ // Expose 'level' attribute on nested lists. We support two hierarchies:
+ // a) list -> listitem -> list -> listitem (nested list is a last child
+ // of listitem of the parent list);
+ // b) list -> listitem -> group -> listitem (nested listitems are contained
+ // by group that is a last child of the parent listitem).
+
+ // Calculate 'level' attribute based on number of parent listitems.
+ level = 0;
+ Accessible* parent = this;
+ while ((parent = parent->Parent())) {
+ roles::Role parentRole = parent->Role();
+
+ if (parentRole == roles::LISTITEM)
+ ++ level;
+ else if (parentRole != roles::LIST && parentRole != roles::GROUPING)
+ break;
+ }
+
+ if (level == 0) {
+ // If this listitem is on top of nested lists then expose 'level'
+ // attribute.
+ parent = Parent();
+ uint32_t siblingCount = parent->ChildCount();
+ for (uint32_t siblingIdx = 0; siblingIdx < siblingCount; siblingIdx++) {
+ Accessible* sibling = parent->GetChildAt(siblingIdx);
+
+ Accessible* siblingChild = sibling->LastChild();
+ if (siblingChild) {
+ roles::Role lastChildRole = siblingChild->Role();
+ if (lastChildRole == roles::LIST || lastChildRole == roles::GROUPING)
+ return 1;
+ }
+ }
+ } else {
+ ++ level; // level is 1-index based
+ }
+ }
+
+ return level;
+}
+
+void
+Accessible::StaticAsserts() const
+{
+ static_assert(eLastStateFlag <= (1 << kStateFlagsBits) - 1,
+ "Accessible::mStateFlags was oversized by eLastStateFlag!");
+ static_assert(eLastAccType <= (1 << kTypeBits) - 1,
+ "Accessible::mType was oversized by eLastAccType!");
+ static_assert(eLastContextFlag <= (1 << kContextFlagsBits) - 1,
+ "Accessible::mContextFlags was oversized by eLastContextFlag!");
+ static_assert(eLastAccGenericType <= (1 << kGenericTypesBits) - 1,
+ "Accessible::mGenericType was oversized by eLastAccGenericType!");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// KeyBinding class
+
+// static
+uint32_t
+KeyBinding::AccelModifier()
+{
+ switch (WidgetInputEvent::AccelModifier()) {
+ case MODIFIER_ALT:
+ return kAlt;
+ case MODIFIER_CONTROL:
+ return kControl;
+ case MODIFIER_META:
+ return kMeta;
+ case MODIFIER_OS:
+ return kOS;
+ default:
+ MOZ_CRASH("Handle the new result of WidgetInputEvent::AccelModifier()");
+ return 0;
+ }
+}
+
+void
+KeyBinding::ToPlatformFormat(nsAString& aValue) const
+{
+ nsCOMPtr<nsIStringBundle> keyStringBundle;
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ mozilla::services::GetStringBundleService();
+ if (stringBundleService)
+ stringBundleService->CreateBundle(
+ "chrome://global-platform/locale/platformKeys.properties",
+ getter_AddRefs(keyStringBundle));
+
+ if (!keyStringBundle)
+ return;
+
+ nsAutoString separator;
+ keyStringBundle->GetStringFromName(u"MODIFIER_SEPARATOR",
+ getter_Copies(separator));
+
+ nsAutoString modifierName;
+ if (mModifierMask & kControl) {
+ keyStringBundle->GetStringFromName(u"VK_CONTROL",
+ getter_Copies(modifierName));
+
+ aValue.Append(modifierName);
+ aValue.Append(separator);
+ }
+
+ if (mModifierMask & kAlt) {
+ keyStringBundle->GetStringFromName(u"VK_ALT",
+ getter_Copies(modifierName));
+
+ aValue.Append(modifierName);
+ aValue.Append(separator);
+ }
+
+ if (mModifierMask & kShift) {
+ keyStringBundle->GetStringFromName(u"VK_SHIFT",
+ getter_Copies(modifierName));
+
+ aValue.Append(modifierName);
+ aValue.Append(separator);
+ }
+
+ if (mModifierMask & kMeta) {
+ keyStringBundle->GetStringFromName(u"VK_META",
+ getter_Copies(modifierName));
+
+ aValue.Append(modifierName);
+ aValue.Append(separator);
+ }
+
+ aValue.Append(mKey);
+}
+
+void
+KeyBinding::ToAtkFormat(nsAString& aValue) const
+{
+ nsAutoString modifierName;
+ if (mModifierMask & kControl)
+ aValue.AppendLiteral("<Control>");
+
+ if (mModifierMask & kAlt)
+ aValue.AppendLiteral("<Alt>");
+
+ if (mModifierMask & kShift)
+ aValue.AppendLiteral("<Shift>");
+
+ if (mModifierMask & kMeta)
+ aValue.AppendLiteral("<Meta>");
+
+ aValue.Append(mKey);
+}
diff --git a/accessible/generic/Accessible.h b/accessible/generic/Accessible.h
new file mode 100644
index 000000000..eaf041920
--- /dev/null
+++ b/accessible/generic/Accessible.h
@@ -0,0 +1,1269 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 _Accessible_H_
+#define _Accessible_H_
+
+#include "mozilla/a11y/AccTypes.h"
+#include "mozilla/a11y/RelationType.h"
+#include "mozilla/a11y/Role.h"
+#include "mozilla/a11y/States.h"
+
+#include "mozilla/UniquePtr.h"
+
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsRefPtrHashtable.h"
+#include "nsRect.h"
+
+struct nsRoleMapEntry;
+
+struct nsRect;
+class nsIFrame;
+class nsIAtom;
+class nsIPersistentProperties;
+
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+class AccEvent;
+class AccGroupInfo;
+class ApplicationAccessible;
+class DocAccessible;
+class EmbeddedObjCollector;
+class EventTree;
+class HTMLImageMapAccessible;
+class HTMLLIAccessible;
+class HyperTextAccessible;
+class ImageAccessible;
+class KeyBinding;
+class OuterDocAccessible;
+class ProxyAccessible;
+class Relation;
+class RootAccessible;
+class TableAccessible;
+class TableCellAccessible;
+class TextLeafAccessible;
+class XULLabelAccessible;
+class XULTreeAccessible;
+
+#ifdef A11Y_LOG
+namespace logging {
+ typedef const char* (*GetTreePrefix)(void* aData, Accessible*);
+ void Tree(const char* aTitle, const char* aMsgText, Accessible* aRoot,
+ GetTreePrefix aPrefixFunc, void* GetTreePrefixData);
+};
+#endif
+
+/**
+ * Name type flags.
+ */
+enum ENameValueFlag {
+ /**
+ * Name either
+ * a) present (not empty): !name.IsEmpty()
+ * b) no name (was missed): name.IsVoid()
+ */
+ eNameOK,
+
+ /**
+ * Name was left empty by the author on purpose:
+ * name.IsEmpty() && !name.IsVoid().
+ */
+ eNoNameOnPurpose,
+
+ /**
+ * Name was computed from the subtree.
+ */
+ eNameFromSubtree,
+
+ /**
+ * Tooltip was used as a name.
+ */
+ eNameFromTooltip
+};
+
+/**
+ * Group position (level, position in set and set size).
+ */
+struct GroupPos
+{
+ GroupPos() : level(0), posInSet(0), setSize(0) { }
+ GroupPos(int32_t aLevel, int32_t aPosInSet, int32_t aSetSize) :
+ level(aLevel), posInSet(aPosInSet), setSize(aSetSize) { }
+
+ int32_t level;
+ int32_t posInSet;
+ int32_t setSize;
+};
+
+/**
+ * An index type. Assert if out of range value was attempted to be used.
+ */
+class index_t
+{
+public:
+ MOZ_IMPLICIT index_t(int32_t aVal) : mVal(aVal) {}
+
+ operator uint32_t() const
+ {
+ MOZ_ASSERT(mVal >= 0, "Attempt to use wrong index!");
+ return mVal;
+ }
+
+ bool IsValid() const { return mVal >= 0; }
+
+private:
+ int32_t mVal;
+};
+
+typedef nsRefPtrHashtable<nsPtrHashKey<const void>, Accessible>
+ AccessibleHashtable;
+
+
+#define NS_ACCESSIBLE_IMPL_IID \
+{ /* 133c8bf4-4913-4355-bd50-426bd1d6e1ad */ \
+ 0x133c8bf4, \
+ 0x4913, \
+ 0x4355, \
+ { 0xbd, 0x50, 0x42, 0x6b, 0xd1, 0xd6, 0xe1, 0xad } \
+}
+
+class Accessible : public nsISupports
+{
+public:
+ Accessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(Accessible)
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ACCESSIBLE_IMPL_IID)
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Public methods
+
+ /**
+ * Return the document accessible for this accessible.
+ */
+ DocAccessible* Document() const { return mDoc; }
+
+ /**
+ * Return the root document accessible for this accessible.
+ */
+ a11y::RootAccessible* RootAccessible() const;
+
+ /**
+ * Return frame for this accessible.
+ */
+ virtual nsIFrame* GetFrame() const;
+
+ /**
+ * Return DOM node associated with the accessible.
+ */
+ virtual nsINode* GetNode() const;
+ inline already_AddRefed<nsIDOMNode> DOMNode() const
+ {
+ nsCOMPtr<nsIDOMNode> DOMNode = do_QueryInterface(GetNode());
+ return DOMNode.forget();
+ }
+ nsIContent* GetContent() const { return mContent; }
+ mozilla::dom::Element* Elm() const
+ { return mContent && mContent->IsElement() ? mContent->AsElement() : nullptr; }
+
+ /**
+ * Return node type information of DOM node associated with the accessible.
+ */
+ bool IsContent() const
+ { return GetNode() && GetNode()->IsNodeOfType(nsINode::eCONTENT); }
+
+ /**
+ * Return the unique identifier of the accessible.
+ */
+ void* UniqueID() { return static_cast<void*>(this); }
+
+ /**
+ * Return language associated with the accessible.
+ */
+ void Language(nsAString& aLocale);
+
+ /**
+ * Get the description of this accessible.
+ */
+ virtual void Description(nsString& aDescription);
+
+ /**
+ * Get the value of this accessible.
+ */
+ virtual void Value(nsString& aValue);
+
+ /**
+ * Get help string for the accessible.
+ */
+ void Help(nsString& aHelp) const { aHelp.Truncate(); }
+
+ /**
+ * Get the name of this accessible.
+ *
+ * Note: aName.IsVoid() when name was left empty by the author on purpose.
+ * aName.IsEmpty() when the author missed name, AT can try to repair a name.
+ */
+ virtual ENameValueFlag Name(nsString& aName);
+
+ /**
+ * Maps ARIA state attributes to state of accessible. Note the given state
+ * argument should hold states for accessible before you pass it into this
+ * method.
+ *
+ * @param [in/out] where to fill the states into.
+ */
+ virtual void ApplyARIAState(uint64_t* aState) const;
+
+ /**
+ * Return enumerated accessible role (see constants in Role.h).
+ */
+ mozilla::a11y::role Role();
+
+ /**
+ * Return true if ARIA role is specified on the element.
+ */
+ bool HasARIARole() const;
+ bool IsARIARole(nsIAtom* aARIARole) const;
+ bool HasStrongARIARole() const;
+
+ /**
+ * Retrun ARIA role map if any.
+ */
+ const nsRoleMapEntry* ARIARoleMap() const;
+
+ /**
+ * Return accessible role specified by ARIA (see constants in
+ * roles).
+ */
+ mozilla::a11y::role ARIARole();
+
+ /**
+ * Return a landmark role if applied.
+ */
+ virtual nsIAtom* LandmarkRole() const;
+
+ /**
+ * Returns enumerated accessible role from native markup (see constants in
+ * Role.h). Doesn't take into account ARIA roles.
+ */
+ virtual mozilla::a11y::role NativeRole();
+
+ /**
+ * Return all states of accessible (including ARIA states).
+ */
+ virtual uint64_t State();
+
+ /**
+ * Return interactive states present on the accessible
+ * (@see NativeInteractiveState).
+ */
+ uint64_t InteractiveState() const
+ {
+ uint64_t state = NativeInteractiveState();
+ ApplyARIAState(&state);
+ return state;
+ }
+
+ /**
+ * Return link states present on the accessible.
+ */
+ uint64_t LinkState() const
+ {
+ uint64_t state = NativeLinkState();
+ ApplyARIAState(&state);
+ return state;
+ }
+
+ /**
+ * Return if accessible is unavailable.
+ */
+ bool Unavailable() const
+ {
+ uint64_t state = NativelyUnavailable() ? states::UNAVAILABLE : 0;
+ ApplyARIAState(&state);
+ return state & states::UNAVAILABLE;
+ }
+
+ /**
+ * Return the states of accessible, not taking into account ARIA states.
+ * Use State() to get complete set of states.
+ */
+ virtual uint64_t NativeState();
+
+ /**
+ * Return native interactice state (unavailable, focusable or selectable).
+ */
+ virtual uint64_t NativeInteractiveState() const;
+
+ /**
+ * Return native link states present on the accessible.
+ */
+ virtual uint64_t NativeLinkState() const;
+
+ /**
+ * Return bit set of invisible and offscreen states.
+ */
+ uint64_t VisibilityState();
+
+ /**
+ * Return true if native unavailable state present.
+ */
+ virtual bool NativelyUnavailable() const;
+
+ /**
+ * Return object attributes for the accessible.
+ */
+ virtual already_AddRefed<nsIPersistentProperties> Attributes();
+
+ /**
+ * Return group position (level, position in set and set size).
+ */
+ virtual mozilla::a11y::GroupPos GroupPosition();
+
+ /**
+ * Used by ChildAtPoint() method to get direct or deepest child at point.
+ */
+ enum EWhichChildAtPoint {
+ eDirectChild,
+ eDeepestChild
+ };
+
+ /**
+ * Return direct or deepest child at the given point.
+ *
+ * @param aX [in] x coordinate relative screen
+ * @param aY [in] y coordinate relative screen
+ * @param aWhichChild [in] flag points if deepest or direct child
+ * should be returned
+ */
+ virtual Accessible* ChildAtPoint(int32_t aX, int32_t aY,
+ EWhichChildAtPoint aWhichChild);
+
+ /**
+ * Return the focused child if any.
+ */
+ virtual Accessible* FocusedChild();
+
+ /**
+ * Return calculated group level based on accessible hierarchy.
+ */
+ virtual int32_t GetLevelInternal();
+
+ /**
+ * Calculate position in group and group size ('posinset' and 'setsize') based
+ * on accessible hierarchy.
+ *
+ * @param aPosInSet [out] accessible position in the group
+ * @param aSetSize [out] the group size
+ */
+ virtual void GetPositionAndSizeInternal(int32_t *aPosInSet,
+ int32_t *aSetSize);
+
+ /**
+ * Get the relation of the given type.
+ */
+ virtual Relation RelationByType(RelationType aType);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Initializing methods
+
+ /**
+ * Shutdown this accessible object.
+ */
+ virtual void Shutdown();
+
+ /**
+ * Set the ARIA role map entry for a new accessible.
+ */
+ void SetRoleMapEntry(const nsRoleMapEntry* aRoleMapEntry);
+
+ /**
+ * Append/insert/remove a child. Return true if operation was successful.
+ */
+ bool AppendChild(Accessible* aChild)
+ { return InsertChildAt(mChildren.Length(), aChild); }
+ virtual bool InsertChildAt(uint32_t aIndex, Accessible* aChild);
+
+ /**
+ * Inserts a child after given sibling. If the child cannot be inserted,
+ * then the child is unbound from the document, and false is returned. Make
+ * sure to null out any references on the child object as it may be destroyed.
+ */
+ bool InsertAfter(Accessible* aNewChild, Accessible* aRefChild);
+
+ virtual bool RemoveChild(Accessible* aChild);
+
+ /**
+ * Reallocates the child withing its parent.
+ */
+ void MoveChild(uint32_t aNewIndex, Accessible* aChild);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Accessible tree traverse methods
+
+ /**
+ * Return parent accessible.
+ */
+ Accessible* Parent() const { return mParent; }
+
+ /**
+ * Return child accessible at the given index.
+ */
+ virtual Accessible* GetChildAt(uint32_t aIndex) const;
+
+ /**
+ * Return child accessible count.
+ */
+ virtual uint32_t ChildCount() const;
+
+ /**
+ * Return index of the given child accessible.
+ */
+ int32_t GetIndexOf(const Accessible* aChild) const
+ { return (aChild->mParent != this) ? -1 : aChild->IndexInParent(); }
+
+ /**
+ * Return index in parent accessible.
+ */
+ virtual int32_t IndexInParent() const;
+
+ /**
+ * Return true if accessible has children;
+ */
+ bool HasChildren() { return !!GetChildAt(0); }
+
+ /**
+ * Return first/last/next/previous sibling of the accessible.
+ */
+ inline Accessible* NextSibling() const
+ { return GetSiblingAtOffset(1); }
+ inline Accessible* PrevSibling() const
+ { return GetSiblingAtOffset(-1); }
+ inline Accessible* FirstChild()
+ { return GetChildAt(0); }
+ inline Accessible* LastChild()
+ {
+ uint32_t childCount = ChildCount();
+ return childCount != 0 ? GetChildAt(childCount - 1) : nullptr;
+ }
+
+ /**
+ * Return embedded accessible children count.
+ */
+ uint32_t EmbeddedChildCount();
+
+ /**
+ * Return embedded accessible child at the given index.
+ */
+ Accessible* GetEmbeddedChildAt(uint32_t aIndex);
+
+ /**
+ * Return index of the given embedded accessible child.
+ */
+ int32_t GetIndexOfEmbeddedChild(Accessible* aChild);
+
+ /**
+ * Return number of content children/content child at index. The content
+ * child is created from markup in contrast to it's never constructed by its
+ * parent accessible (like treeitem accessibles for XUL trees).
+ */
+ uint32_t ContentChildCount() const { return mChildren.Length(); }
+ Accessible* ContentChildAt(uint32_t aIndex) const
+ { return mChildren.ElementAt(aIndex); }
+
+ /**
+ * Return true if the accessible is attached to tree.
+ */
+ bool IsBoundToParent() const { return !!mParent; }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Miscellaneous methods
+
+ /**
+ * Handle accessible event, i.e. process it, notifies observers and fires
+ * platform specific event.
+ */
+ virtual nsresult HandleAccEvent(AccEvent* aAccEvent);
+
+ /**
+ * Return true if the accessible is an acceptable child.
+ */
+ virtual bool IsAcceptableChild(nsIContent* aEl) const
+ { return true; }
+
+ /**
+ * Returns text of accessible if accessible has text role otherwise empty
+ * string.
+ *
+ * @param aText [in] returned text of the accessible
+ * @param aStartOffset [in, optional] start offset inside of the accessible,
+ * if missed entire text is appended
+ * @param aLength [in, optional] required length of text, if missed
+ * then text form start offset till the end is appended
+ */
+ virtual void AppendTextTo(nsAString& aText, uint32_t aStartOffset = 0,
+ uint32_t aLength = UINT32_MAX);
+
+ /**
+ * Return boundaries in screen coordinates.
+ */
+ virtual nsIntRect Bounds() const;
+
+ /**
+ * Return boundaries rect relative the bounding frame.
+ */
+ virtual nsRect RelativeBounds(nsIFrame** aRelativeFrame) const;
+
+ /**
+ * Selects the accessible within its container if applicable.
+ */
+ virtual void SetSelected(bool aSelect);
+
+ /**
+ * Select the accessible within its container.
+ */
+ void TakeSelection();
+
+ /**
+ * Focus the accessible.
+ */
+ virtual void TakeFocus();
+
+ /**
+ * Scroll the accessible into view.
+ */
+ void ScrollTo(uint32_t aHow) const;
+
+ /**
+ * Scroll the accessible to the given point.
+ */
+ void ScrollToPoint(uint32_t aCoordinateType, int32_t aX, int32_t aY);
+
+ /**
+ * Get a pointer to accessibility interface for this node, which is specific
+ * to the OS/accessibility toolkit we're running on.
+ */
+ virtual void GetNativeInterface(void** aNativeAccessible);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Downcasting and types
+
+ inline bool IsAbbreviation() const
+ {
+ return mContent->IsAnyOfHTMLElements(nsGkAtoms::abbr, nsGkAtoms::acronym);
+ }
+
+ bool IsAlert() const { return HasGenericType(eAlert); }
+
+ bool IsApplication() const { return mType == eApplicationType; }
+ ApplicationAccessible* AsApplication();
+
+ bool IsAutoComplete() const { return HasGenericType(eAutoComplete); }
+
+ bool IsAutoCompletePopup() const
+ { return HasGenericType(eAutoCompletePopup); }
+
+ bool IsButton() const { return HasGenericType(eButton); }
+
+ bool IsCombobox() const { return HasGenericType(eCombobox); }
+
+ bool IsDoc() const { return HasGenericType(eDocument); }
+ DocAccessible* AsDoc();
+
+ bool IsGenericHyperText() const { return mType == eHyperTextType; }
+ bool IsHyperText() const { return HasGenericType(eHyperText); }
+ HyperTextAccessible* AsHyperText();
+
+ bool IsHTMLBr() const { return mType == eHTMLBRType; }
+ bool IsHTMLCaption() const { return mType == eHTMLCaptionType; }
+ bool IsHTMLCombobox() const { return mType == eHTMLComboboxType; }
+ bool IsHTMLFileInput() const { return mType == eHTMLFileInputType; }
+
+ bool IsHTMLListItem() const { return mType == eHTMLLiType; }
+ HTMLLIAccessible* AsHTMLListItem();
+
+ bool IsHTMLOptGroup() const { return mType == eHTMLOptGroupType; }
+
+ bool IsHTMLTable() const { return mType == eHTMLTableType; }
+ bool IsHTMLTableRow() const { return mType == eHTMLTableRowType; }
+
+ bool IsImage() const { return mType == eImageType; }
+ ImageAccessible* AsImage();
+
+ bool IsImageMap() const { return mType == eImageMapType; }
+ HTMLImageMapAccessible* AsImageMap();
+
+ bool IsList() const { return HasGenericType(eList); }
+
+ bool IsListControl() const { return HasGenericType(eListControl); }
+
+ bool IsMenuButton() const { return HasGenericType(eMenuButton); }
+
+ bool IsMenuPopup() const { return mType == eMenuPopupType; }
+
+ bool IsProxy() const { return mType == eProxyType; }
+ ProxyAccessible* Proxy() const
+ {
+ MOZ_ASSERT(IsProxy());
+ return mBits.proxy;
+ }
+ uint32_t ProxyInterfaces() const
+ {
+ MOZ_ASSERT(IsProxy());
+ return mInt.mProxyInterfaces;
+ }
+ void SetProxyInterfaces(uint32_t aInterfaces)
+ {
+ MOZ_ASSERT(IsProxy());
+ mInt.mProxyInterfaces = aInterfaces;
+ }
+
+ bool IsOuterDoc() const { return mType == eOuterDocType; }
+ OuterDocAccessible* AsOuterDoc();
+
+ bool IsProgress() const { return mType == eProgressType; }
+
+ bool IsRoot() const { return mType == eRootType; }
+ a11y::RootAccessible* AsRoot();
+
+ bool IsSearchbox() const;
+
+ bool IsSelect() const { return HasGenericType(eSelect); }
+
+ bool IsTable() const { return HasGenericType(eTable); }
+ virtual TableAccessible* AsTable() { return nullptr; }
+
+ bool IsTableCell() const { return HasGenericType(eTableCell); }
+ virtual TableCellAccessible* AsTableCell() { return nullptr; }
+ const TableCellAccessible* AsTableCell() const
+ { return const_cast<Accessible*>(this)->AsTableCell(); }
+
+ bool IsTableRow() const { return HasGenericType(eTableRow); }
+
+ bool IsTextField() const { return mType == eHTMLTextFieldType; }
+
+ bool IsText() const { return mGenericTypes & eText; }
+
+ bool IsTextLeaf() const { return mType == eTextLeafType; }
+ TextLeafAccessible* AsTextLeaf();
+
+ bool IsXULLabel() const { return mType == eXULLabelType; }
+ XULLabelAccessible* AsXULLabel();
+
+ bool IsXULListItem() const { return mType == eXULListItemType; }
+
+ bool IsXULTabpanels() const { return mType == eXULTabpanelsType; }
+
+ bool IsXULTree() const { return mType == eXULTreeType; }
+ XULTreeAccessible* AsXULTree();
+
+ /**
+ * Return true if the accessible belongs to the given accessible type.
+ */
+ bool HasGenericType(AccGenericType aType) const;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // ActionAccessible
+
+ /**
+ * Return the number of actions that can be performed on this accessible.
+ */
+ virtual uint8_t ActionCount();
+
+ /**
+ * Return action name at given index.
+ */
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName);
+
+ /**
+ * Default to localized action name.
+ */
+ void ActionDescriptionAt(uint8_t aIndex, nsAString& aDescription)
+ {
+ nsAutoString name;
+ ActionNameAt(aIndex, name);
+ TranslateString(name, aDescription);
+ }
+
+ /**
+ * Invoke the accessible action.
+ */
+ virtual bool DoAction(uint8_t aIndex);
+
+ /**
+ * Return access key, such as Alt+D.
+ */
+ virtual KeyBinding AccessKey() const;
+
+ /**
+ * Return global keyboard shortcut for default action, such as Ctrl+O for
+ * Open file menuitem.
+ */
+ virtual KeyBinding KeyboardShortcut() const;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // HyperLinkAccessible (any embedded object in text can implement HyperLink,
+ // which helps determine where it is located within containing text).
+
+ /**
+ * Return true if the accessible is hyper link accessible.
+ */
+ virtual bool IsLink();
+
+ /**
+ * Return the start offset of the link within the parent accessible.
+ */
+ virtual uint32_t StartOffset();
+
+ /**
+ * Return the end offset of the link within the parent accessible.
+ */
+ virtual uint32_t EndOffset();
+
+ /**
+ * Return true if the link is valid (e. g. points to a valid URL).
+ */
+ inline bool IsLinkValid()
+ {
+ NS_PRECONDITION(IsLink(), "IsLinkValid is called on not hyper link!");
+
+ // XXX In order to implement this we would need to follow every link
+ // Perhaps we can get information about invalid links from the cache
+ // In the mean time authors can use role="link" aria-invalid="true"
+ // to force it for links they internally know to be invalid
+ return (0 == (State() & mozilla::a11y::states::INVALID));
+ }
+
+ /**
+ * Return the number of anchors within the link.
+ */
+ virtual uint32_t AnchorCount();
+
+ /**
+ * Returns an anchor accessible at the given index.
+ */
+ virtual Accessible* AnchorAt(uint32_t aAnchorIndex);
+
+ /**
+ * Returns an anchor URI at the given index.
+ */
+ virtual already_AddRefed<nsIURI> AnchorURIAt(uint32_t aAnchorIndex);
+
+ /**
+ * Returns a text point for the accessible element.
+ */
+ void ToTextPoint(HyperTextAccessible** aContainer, int32_t* aOffset,
+ bool aIsBefore = true) const;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // SelectAccessible
+
+ /**
+ * Return an array of selected items.
+ */
+ virtual void SelectedItems(nsTArray<Accessible*>* aItems);
+
+ /**
+ * Return the number of selected items.
+ */
+ virtual uint32_t SelectedItemCount();
+
+ /**
+ * Return selected item at the given index.
+ */
+ virtual Accessible* GetSelectedItem(uint32_t aIndex);
+
+ /**
+ * Determine if item at the given index is selected.
+ */
+ virtual bool IsItemSelected(uint32_t aIndex);
+
+ /**
+ * Add item at the given index the selection. Return true if success.
+ */
+ virtual bool AddItemToSelection(uint32_t aIndex);
+
+ /**
+ * Remove item at the given index from the selection. Return if success.
+ */
+ virtual bool RemoveItemFromSelection(uint32_t aIndex);
+
+ /**
+ * Select all items. Return true if success.
+ */
+ virtual bool SelectAll();
+
+ /**
+ * Unselect all items. Return true if success.
+ */
+ virtual bool UnselectAll();
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Value (numeric value interface)
+
+ virtual double MaxValue() const;
+ virtual double MinValue() const;
+ virtual double CurValue() const;
+ virtual double Step() const;
+ virtual bool SetCurValue(double aValue);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Widgets
+
+ /**
+ * Return true if accessible is a widget, i.e. control or accessible that
+ * manages its items. Note, being a widget the accessible may be a part of
+ * composite widget.
+ */
+ virtual bool IsWidget() const;
+
+ /**
+ * Return true if the widget is active, i.e. has a focus within it.
+ */
+ virtual bool IsActiveWidget() const;
+
+ /**
+ * Return true if the widget has items and items are operable by user and
+ * can be activated.
+ */
+ virtual bool AreItemsOperable() const;
+
+ /**
+ * Return the current item of the widget, i.e. an item that has or will have
+ * keyboard focus when widget gets active.
+ */
+ virtual Accessible* CurrentItem();
+
+ /**
+ * Set the current item of the widget.
+ */
+ virtual void SetCurrentItem(Accessible* aItem);
+
+ /**
+ * Return container widget this accessible belongs to.
+ */
+ virtual Accessible* ContainerWidget() const;
+
+ /**
+ * Return the localized string for the given key.
+ */
+ static void TranslateString(const nsString& aKey, nsAString& aStringOut);
+
+ /**
+ * Return true if the accessible is defunct.
+ */
+ bool IsDefunct() const { return mStateFlags & eIsDefunct; }
+
+ /**
+ * Return false if the accessible is no longer in the document.
+ */
+ bool IsInDocument() const { return !(mStateFlags & eIsNotInDocument); }
+
+ /**
+ * Return true if the accessible should be contained by document node map.
+ */
+ bool IsNodeMapEntry() const
+ { return HasOwnContent() && !(mStateFlags & eNotNodeMapEntry); }
+
+ /**
+ * Return true if the accessible's group info needs to be updated.
+ */
+ inline bool HasDirtyGroupInfo() const { return mStateFlags & eGroupInfoDirty; }
+
+ /**
+ * Return true if the accessible has associated DOM content.
+ */
+ bool HasOwnContent() const
+ { return mContent && !(mStateFlags & eSharedNode); }
+
+ /**
+ * Return true if the accessible has a numeric value.
+ */
+ bool HasNumericValue() const;
+
+ /**
+ * Return true if the accessible state change is processed by handling proper
+ * DOM UI event, if otherwise then false. For example, HTMLCheckboxAccessible
+ * process nsIDocumentObserver::ContentStateChanged instead
+ * 'CheckboxStateChange' event.
+ */
+ bool NeedsDOMUIEvent() const
+ { return !(mStateFlags & eIgnoreDOMUIEvent); }
+
+ /**
+ * Get/set survivingInUpdate bit on child indicating that parent recollects
+ * its children.
+ */
+ bool IsSurvivingInUpdate() const { return mStateFlags & eSurvivingInUpdate; }
+ void SetSurvivingInUpdate(bool aIsSurviving)
+ {
+ if (aIsSurviving)
+ mStateFlags |= eSurvivingInUpdate;
+ else
+ mStateFlags &= ~eSurvivingInUpdate;
+ }
+
+ /**
+ * Get/set repositioned bit indicating that the accessible was moved in
+ * the accessible tree, i.e. the accessible tree structure differs from DOM.
+ */
+ bool IsRelocated() const { return mStateFlags & eRelocated; }
+ void SetRelocated(bool aRelocated)
+ {
+ if (aRelocated)
+ mStateFlags |= eRelocated;
+ else
+ mStateFlags &= ~eRelocated;
+ }
+
+ /**
+ * Return true if the accessible doesn't allow accessible children from XBL
+ * anonymous subtree.
+ */
+ bool NoXBLKids() const { return mStateFlags & eNoXBLKids; }
+
+ /**
+ * Return true if the accessible allows accessible children from subtree of
+ * a DOM element of this accessible.
+ */
+ bool KidsFromDOM() const { return !(mStateFlags & eNoKidsFromDOM); }
+
+ /**
+ * Return true if this accessible has a parent whose name depends on this
+ * accessible.
+ */
+ bool HasNameDependentParent() const
+ { return mContextFlags & eHasNameDependentParent; }
+
+ /**
+ * Return true if aria-hidden="true" is applied to the accessible or inherited
+ * from the parent.
+ */
+ bool IsARIAHidden() const { return mContextFlags & eARIAHidden; }
+ void SetARIAHidden(bool aIsDefined);
+
+ /**
+ * Return true if the element is inside an alert.
+ */
+ bool IsInsideAlert() const { return mContextFlags & eInsideAlert; }
+
+ /**
+ * Return true if there is a pending reorder event for this accessible.
+ */
+ bool ReorderEventTarget() const { return mReorderEventTarget; }
+
+ /**
+ * Return true if there is a pending show event for this accessible.
+ */
+ bool ShowEventTarget() const { return mShowEventTarget; }
+
+ /**
+ * Return true if there is a pending hide event for this accessible.
+ */
+ bool HideEventTarget() const { return mHideEventTarget; }
+
+ /**
+ * Set if there is a pending reorder event for this accessible.
+ */
+ void SetReorderEventTarget(bool aTarget) { mReorderEventTarget = aTarget; }
+
+ /**
+ * Set if this accessible is a show event target.
+ */
+ void SetShowEventTarget(bool aTarget) { mShowEventTarget = aTarget; }
+
+ /**
+ * Set if this accessible is a hide event target.
+ */
+ void SetHideEventTarget(bool aTarget) { mHideEventTarget = aTarget; }
+
+protected:
+ virtual ~Accessible();
+
+ /**
+ * Return the accessible name provided by native markup. It doesn't take
+ * into account ARIA markup used to specify the name.
+ */
+ virtual mozilla::a11y::ENameValueFlag NativeName(nsString& aName);
+
+ /**
+ * Return the accessible description provided by native markup. It doesn't take
+ * into account ARIA markup used to specify the description.
+ */
+ virtual void NativeDescription(nsString& aDescription);
+
+ /**
+ * Return object attributes provided by native markup. It doesn't take into
+ * account ARIA.
+ */
+ virtual already_AddRefed<nsIPersistentProperties> NativeAttributes();
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Initializing, cache and tree traverse methods
+
+ /**
+ * Destroy the object.
+ */
+ void LastRelease();
+
+ /**
+ * Set accessible parent and index in parent.
+ */
+ void BindToParent(Accessible* aParent, uint32_t aIndexInParent);
+ void UnbindFromParent();
+
+ /**
+ * Return sibling accessible at the given offset.
+ */
+ virtual Accessible* GetSiblingAtOffset(int32_t aOffset,
+ nsresult *aError = nullptr) const;
+
+ /**
+ * Flags used to describe the state of this accessible.
+ */
+ enum StateFlags {
+ eIsDefunct = 1 << 0, // accessible is defunct
+ eIsNotInDocument = 1 << 1, // accessible is not in document
+ eSharedNode = 1 << 2, // accessible shares DOM node from another accessible
+ eNotNodeMapEntry = 1 << 3, // accessible shouldn't be in document node map
+ eHasNumericValue = 1 << 4, // accessible has a numeric value
+ eGroupInfoDirty = 1 << 5, // accessible needs to update group info
+ eKidsMutating = 1 << 6, // subtree is being mutated
+ eIgnoreDOMUIEvent = 1 << 7, // don't process DOM UI events for a11y events
+ eSurvivingInUpdate = 1 << 8, // parent drops children to recollect them
+ eRelocated = 1 << 9, // accessible was moved in tree
+ eNoXBLKids = 1 << 10, // accessible don't allows XBL children
+ eNoKidsFromDOM = 1 << 11, // accessible doesn't allow children from DOM
+ eHasTextKids = 1 << 12, // accessible have a text leaf in children
+
+ eLastStateFlag = eNoKidsFromDOM
+ };
+
+ /**
+ * Flags used for contextual information about the accessible.
+ */
+ enum ContextFlags {
+ eHasNameDependentParent = 1 << 0, // Parent's name depends on this accessible.
+ eARIAHidden = 1 << 1,
+ eInsideAlert = 1 << 2,
+
+ eLastContextFlag = eInsideAlert
+ };
+
+protected:
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Miscellaneous helpers
+
+ /**
+ * Return ARIA role (helper method).
+ */
+ mozilla::a11y::role ARIATransformRole(mozilla::a11y::role aRole);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Name helpers
+
+ /**
+ * Returns the accessible name specified by ARIA.
+ */
+ void ARIAName(nsString& aName);
+
+ /**
+ * Return the name for XUL element.
+ */
+ static void XULElmName(DocAccessible* aDocument,
+ nsIContent* aElm, nsString& aName);
+
+ // helper method to verify frames
+ static nsresult GetFullKeyName(const nsAString& aModifierName, const nsAString& aKeyName, nsAString& aStringOut);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Action helpers
+
+ /**
+ * Prepares click action that will be invoked in timeout.
+ *
+ * @note DoCommand() prepares an action in timeout because when action
+ * command opens a modal dialog/window, it won't return until the
+ * dialog/window is closed. If executing action command directly in
+ * nsIAccessible::DoAction() method, it will block AT tools (e.g. GOK) that
+ * invoke action of mozilla accessibles direclty (see bug 277888 for details).
+ *
+ * @param aContent [in, optional] element to click
+ * @param aActionIndex [in, optional] index of accessible action
+ */
+ void DoCommand(nsIContent *aContent = nullptr, uint32_t aActionIndex = 0);
+
+ /**
+ * Dispatch click event.
+ */
+ virtual void DispatchClickEvent(nsIContent *aContent, uint32_t aActionIndex);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Helpers
+
+ /**
+ * Get the container node for an atomic region, defined by aria-atomic="true"
+ * @return the container node
+ */
+ nsIContent* GetAtomicRegion() const;
+
+ /**
+ * Return numeric value of the given ARIA attribute, NaN if not applicable.
+ *
+ * @param aARIAProperty [in] the ARIA property we're using
+ * @return a numeric value
+ */
+ double AttrNumericValue(nsIAtom* aARIAAttr) const;
+
+ /**
+ * Return the action rule based on ARIA enum constants EActionRule
+ * (see ARIAMap.h). Used by ActionCount() and ActionNameAt().
+ */
+ uint32_t GetActionRule() const;
+
+ /**
+ * Return group info.
+ */
+ AccGroupInfo* GetGroupInfo();
+
+ // Data Members
+ nsCOMPtr<nsIContent> mContent;
+ RefPtr<DocAccessible> mDoc;
+
+ Accessible* mParent;
+ nsTArray<Accessible*> mChildren;
+ int32_t mIndexInParent;
+
+ static const uint8_t kStateFlagsBits = 13;
+ static const uint8_t kContextFlagsBits = 3;
+ static const uint8_t kTypeBits = 6;
+ static const uint8_t kGenericTypesBits = 16;
+
+ /**
+ * Non-NO_ROLE_MAP_ENTRY_INDEX indicates author-supplied role;
+ * possibly state & value as well
+ */
+ uint8_t mRoleMapEntryIndex;
+
+ /**
+ * Keep in sync with StateFlags, ContextFlags, and AccTypes.
+ */
+ uint32_t mStateFlags : kStateFlagsBits;
+ uint32_t mContextFlags : kContextFlagsBits;
+ uint32_t mType : kTypeBits;
+ uint32_t mGenericTypes : kGenericTypesBits;
+ uint32_t mReorderEventTarget : 1;
+ uint32_t mShowEventTarget : 1;
+ uint32_t mHideEventTarget : 1;
+
+ void StaticAsserts() const;
+
+#ifdef A11Y_LOG
+ friend void logging::Tree(const char* aTitle, const char* aMsgText,
+ Accessible* aRoot,
+ logging::GetTreePrefix aPrefixFunc,
+ void* aGetTreePrefixData);
+#endif
+ friend class DocAccessible;
+ friend class xpcAccessible;
+ friend class TreeMutation;
+
+ UniquePtr<mozilla::a11y::EmbeddedObjCollector> mEmbeddedObjCollector;
+ union {
+ int32_t mIndexOfEmbeddedChild;
+ uint32_t mProxyInterfaces;
+ } mInt;
+
+ friend class EmbeddedObjCollector;
+
+ union
+ {
+ AccGroupInfo* groupInfo;
+ ProxyAccessible* proxy;
+ } mBits;
+ friend class AccGroupInfo;
+
+private:
+ Accessible() = delete;
+ Accessible(const Accessible&) = delete;
+ Accessible& operator =(const Accessible&) = delete;
+
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(Accessible,
+ NS_ACCESSIBLE_IMPL_IID)
+
+
+/**
+ * Represent key binding associated with accessible (such as access key and
+ * global keyboard shortcuts).
+ */
+class KeyBinding
+{
+public:
+ /**
+ * Modifier mask values.
+ */
+ static const uint32_t kShift = 1;
+ static const uint32_t kControl = 2;
+ static const uint32_t kAlt = 4;
+ static const uint32_t kMeta = 8;
+ static const uint32_t kOS = 16;
+
+ static uint32_t AccelModifier();
+
+ KeyBinding() : mKey(0), mModifierMask(0) {}
+ KeyBinding(uint32_t aKey, uint32_t aModifierMask) :
+ mKey(aKey), mModifierMask(aModifierMask) {}
+
+ inline bool IsEmpty() const { return !mKey; }
+ inline uint32_t Key() const { return mKey; }
+ inline uint32_t ModifierMask() const { return mModifierMask; }
+
+ enum Format {
+ ePlatformFormat,
+ eAtkFormat
+ };
+
+ /**
+ * Return formatted string for this key binding depending on the given format.
+ */
+ inline void ToString(nsAString& aValue,
+ Format aFormat = ePlatformFormat) const
+ {
+ aValue.Truncate();
+ AppendToString(aValue, aFormat);
+ }
+ inline void AppendToString(nsAString& aValue,
+ Format aFormat = ePlatformFormat) const
+ {
+ if (mKey) {
+ if (aFormat == ePlatformFormat)
+ ToPlatformFormat(aValue);
+ else
+ ToAtkFormat(aValue);
+ }
+ }
+
+private:
+ void ToPlatformFormat(nsAString& aValue) const;
+ void ToAtkFormat(nsAString& aValue) const;
+
+ uint32_t mKey;
+ uint32_t mModifierMask;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/generic/ApplicationAccessible.cpp b/accessible/generic/ApplicationAccessible.cpp
new file mode 100644
index 000000000..ae8ca27e3
--- /dev/null
+++ b/accessible/generic/ApplicationAccessible.cpp
@@ -0,0 +1,201 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 "nsAccessibilityService.h"
+#include "nsAccUtils.h"
+#include "Relation.h"
+#include "Role.h"
+#include "States.h"
+
+#include "nsIComponentManager.h"
+#include "nsIDOMDocument.h"
+#include "nsIWindowMediator.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Services.h"
+#include "nsIStringBundle.h"
+
+using namespace mozilla::a11y;
+
+ApplicationAccessible::ApplicationAccessible() :
+ AccessibleWrap(nullptr, nullptr)
+{
+ mType = eApplicationType;
+ mAppInfo = do_GetService("@mozilla.org/xre/app-info;1");
+ MOZ_ASSERT(mAppInfo, "no application info");
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(ApplicationAccessible, Accessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIAccessible
+
+ENameValueFlag
+ApplicationAccessible::Name(nsString& aName)
+{
+ aName.Truncate();
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+
+ NS_ASSERTION(bundleService, "String bundle service must be present!");
+ if (!bundleService)
+ return eNameOK;
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = bundleService->CreateBundle("chrome://branding/locale/brand.properties",
+ getter_AddRefs(bundle));
+ if (NS_FAILED(rv))
+ return eNameOK;
+
+ nsXPIDLString appName;
+ rv = bundle->GetStringFromName(u"brandShortName",
+ getter_Copies(appName));
+ if (NS_FAILED(rv) || appName.IsEmpty()) {
+ NS_WARNING("brandShortName not found, using default app name");
+ appName.AssignLiteral("Gecko based application");
+ }
+
+ aName.Assign(appName);
+ return eNameOK;
+}
+
+void
+ApplicationAccessible::Description(nsString& aDescription)
+{
+ aDescription.Truncate();
+}
+
+void
+ApplicationAccessible::Value(nsString& aValue)
+{
+ aValue.Truncate();
+}
+
+uint64_t
+ApplicationAccessible::State()
+{
+ return IsDefunct() ? states::DEFUNCT : 0;
+}
+
+already_AddRefed<nsIPersistentProperties>
+ApplicationAccessible::NativeAttributes()
+{
+ return nullptr;
+}
+
+GroupPos
+ApplicationAccessible::GroupPosition()
+{
+ return GroupPos();
+}
+
+Accessible*
+ApplicationAccessible::ChildAtPoint(int32_t aX, int32_t aY,
+ EWhichChildAtPoint aWhichChild)
+{
+ return nullptr;
+}
+
+Accessible*
+ApplicationAccessible::FocusedChild()
+{
+ Accessible* focus = FocusMgr()->FocusedAccessible();
+ if (focus && focus->Parent() == this)
+ return focus;
+
+ return nullptr;
+}
+
+Relation
+ApplicationAccessible::RelationByType(RelationType aRelationType)
+{
+ return Relation();
+}
+
+nsIntRect
+ApplicationAccessible::Bounds() const
+{
+ return nsIntRect();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible public methods
+
+void
+ApplicationAccessible::Shutdown()
+{
+ mAppInfo = nullptr;
+}
+
+void
+ApplicationAccessible::ApplyARIAState(uint64_t* aState) const
+{
+}
+
+role
+ApplicationAccessible::NativeRole()
+{
+ return roles::APP_ROOT;
+}
+
+uint64_t
+ApplicationAccessible::NativeState()
+{
+ return 0;
+}
+
+KeyBinding
+ApplicationAccessible::AccessKey() const
+{
+ return KeyBinding();
+}
+
+void
+ApplicationAccessible::Init()
+{
+ // Basically children are kept updated by Append/RemoveChild method calls.
+ // However if there are open windows before accessibility was started
+ // then we need to make sure root accessibles for open windows are created so
+ // that all root accessibles are stored in application accessible children
+ // array.
+
+ nsCOMPtr<nsIWindowMediator> windowMediator =
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID);
+
+ nsCOMPtr<nsISimpleEnumerator> windowEnumerator;
+ nsresult rv = windowMediator->GetEnumerator(nullptr,
+ getter_AddRefs(windowEnumerator));
+ if (NS_FAILED(rv))
+ return;
+
+ bool hasMore = false;
+ windowEnumerator->HasMoreElements(&hasMore);
+ while (hasMore) {
+ nsCOMPtr<nsISupports> window;
+ windowEnumerator->GetNext(getter_AddRefs(window));
+ nsCOMPtr<nsPIDOMWindowOuter> DOMWindow = do_QueryInterface(window);
+ if (DOMWindow) {
+ nsCOMPtr<nsIDocument> docNode = DOMWindow->GetDoc();
+ if (docNode) {
+ GetAccService()->GetDocAccessible(docNode); // ensure creation
+ }
+ }
+ windowEnumerator->HasMoreElements(&hasMore);
+ }
+}
+
+Accessible*
+ApplicationAccessible::GetSiblingAtOffset(int32_t aOffset,
+ nsresult* aError) const
+{
+ if (aError)
+ *aError = NS_OK; // fail peacefully
+
+ return nullptr;
+}
diff --git a/accessible/generic/ApplicationAccessible.h b/accessible/generic/ApplicationAccessible.h
new file mode 100644
index 000000000..7609a86e2
--- /dev/null
+++ b/accessible/generic/ApplicationAccessible.h
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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_ApplicationAccessible_h__
+#define mozilla_a11y_ApplicationAccessible_h__
+
+#include "AccessibleWrap.h"
+
+#include "nsIMutableArray.h"
+#include "nsIXULAppInfo.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * ApplicationAccessible is for the whole application of Mozilla.
+ * Only one instance of ApplicationAccessible exists for one Mozilla instance.
+ * And this one should be created when Mozilla Startup (if accessibility
+ * feature has been enabled) and destroyed when Mozilla Shutdown.
+ *
+ * All the accessibility objects for toplevel windows are direct children of
+ * the ApplicationAccessible instance.
+ */
+
+class ApplicationAccessible : public AccessibleWrap
+{
+public:
+
+ ApplicationAccessible();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual void Shutdown() override;
+ virtual nsIntRect Bounds() const override;
+ virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override;
+ virtual GroupPos GroupPosition() override;
+ virtual ENameValueFlag Name(nsString& aName) override;
+ virtual void ApplyARIAState(uint64_t* aState) const override;
+ virtual void Description(nsString& aDescription) override;
+ virtual void Value(nsString& aValue) override;
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual uint64_t State() override;
+ virtual uint64_t NativeState() override;
+ virtual Relation RelationByType(RelationType aType) override;
+
+ virtual Accessible* ChildAtPoint(int32_t aX, int32_t aY,
+ EWhichChildAtPoint aWhichChild) override;
+ virtual Accessible* FocusedChild() override;
+
+ // ActionAccessible
+ virtual KeyBinding AccessKey() const override;
+
+ // ApplicationAccessible
+ void Init();
+
+ void AppName(nsAString& aName) const
+ {
+ MOZ_ASSERT(mAppInfo, "no application info");
+
+ if (mAppInfo) {
+ nsAutoCString cname;
+ mAppInfo->GetName(cname);
+ AppendUTF8toUTF16(cname, aName);
+ }
+ }
+
+ void AppVersion(nsAString& aVersion) const
+ {
+ MOZ_ASSERT(mAppInfo, "no application info");
+
+ if (mAppInfo) {
+ nsAutoCString cversion;
+ mAppInfo->GetVersion(cversion);
+ AppendUTF8toUTF16(cversion, aVersion);
+ }
+ }
+
+ void PlatformName(nsAString& aName) const
+ {
+ aName.AssignLiteral("Gecko");
+ }
+
+ void PlatformVersion(nsAString& aVersion) const
+ {
+ MOZ_ASSERT(mAppInfo, "no application info");
+
+ if (mAppInfo) {
+ nsAutoCString cversion;
+ mAppInfo->GetPlatformVersion(cversion);
+ AppendUTF8toUTF16(cversion, aVersion);
+ }
+ }
+
+protected:
+ virtual ~ApplicationAccessible() {}
+
+ // Accessible
+ virtual Accessible* GetSiblingAtOffset(int32_t aOffset,
+ nsresult *aError = nullptr) const override;
+
+private:
+ nsCOMPtr<nsIXULAppInfo> mAppInfo;
+};
+
+inline ApplicationAccessible*
+Accessible::AsApplication()
+{
+ return IsApplication() ? static_cast<ApplicationAccessible*>(this) : nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/generic/BaseAccessibles.cpp b/accessible/generic/BaseAccessibles.cpp
new file mode 100644
index 000000000..bcb262a97
--- /dev/null
+++ b/accessible/generic/BaseAccessibles.cpp
@@ -0,0 +1,264 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "BaseAccessibles.h"
+
+#include "Accessible-inl.h"
+#include "HyperTextAccessibleWrap.h"
+#include "nsAccessibilityService.h"
+#include "nsAccUtils.h"
+#include "nsCoreUtils.h"
+#include "Role.h"
+#include "States.h"
+#include "nsIURI.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// LeafAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+LeafAccessible::
+ LeafAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+ mStateFlags |= eNoKidsFromDOM;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(LeafAccessible, Accessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// LeafAccessible: Accessible public
+
+Accessible*
+LeafAccessible::ChildAtPoint(int32_t aX, int32_t aY,
+ EWhichChildAtPoint aWhichChild)
+{
+ // Don't walk into leaf accessibles.
+ return this;
+}
+
+bool
+LeafAccessible::InsertChildAt(uint32_t aIndex, Accessible* aChild)
+{
+ NS_NOTREACHED("InsertChildAt called on leaf accessible!");
+ return false;
+}
+
+bool
+LeafAccessible::RemoveChild(Accessible* aChild)
+{
+ NS_NOTREACHED("RemoveChild called on leaf accessible!");
+ return false;
+}
+
+bool
+LeafAccessible::IsAcceptableChild(nsIContent* aEl) const
+{
+ // No children for leaf accessible.
+ return false;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// LinkableAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS_INHERITED0(LinkableAccessible, AccessibleWrap)
+
+////////////////////////////////////////////////////////////////////////////////
+// LinkableAccessible. nsIAccessible
+
+void
+LinkableAccessible::TakeFocus()
+{
+ if (Accessible* actionAcc = ActionWalk()) {
+ actionAcc->TakeFocus();
+ } else {
+ AccessibleWrap::TakeFocus();
+ }
+}
+
+uint64_t
+LinkableAccessible::NativeLinkState() const
+{
+ bool isLink;
+ Accessible* actionAcc =
+ const_cast<LinkableAccessible*>(this)->ActionWalk(&isLink);
+ if (isLink) {
+ return states::LINKED | (actionAcc->LinkState() & states::TRAVERSED);
+ }
+
+ return 0;
+}
+
+void
+LinkableAccessible::Value(nsString& aValue)
+{
+ aValue.Truncate();
+
+ Accessible::Value(aValue);
+ if (!aValue.IsEmpty()) {
+ return;
+ }
+
+ bool isLink;
+ Accessible* actionAcc = ActionWalk(&isLink);
+ if (isLink) {
+ actionAcc->Value(aValue);
+ }
+}
+
+uint8_t
+LinkableAccessible::ActionCount()
+{
+ bool isLink, isOnclick, isLabelWithControl;
+ ActionWalk(&isLink, &isOnclick, &isLabelWithControl);
+ return (isLink || isOnclick || isLabelWithControl) ? 1 : 0;
+}
+
+Accessible*
+LinkableAccessible::ActionWalk(bool* aIsLink, bool* aIsOnclick,
+ bool* aIsLabelWithControl)
+{
+ if (aIsOnclick) {
+ *aIsOnclick = false;
+ }
+ if (aIsLink) {
+ *aIsLink = false;
+ }
+ if (aIsLabelWithControl) {
+ *aIsLabelWithControl = false;
+ }
+
+ if (nsCoreUtils::HasClickListener(mContent)) {
+ if (aIsOnclick) {
+ *aIsOnclick = true;
+ }
+ return nullptr;
+ }
+
+ // XXX: The logic looks broken since the click listener may be registered
+ // on non accessible node in parent chain but this node is skipped when tree
+ // is traversed.
+ Accessible* walkUpAcc = this;
+ while ((walkUpAcc = walkUpAcc->Parent()) && !walkUpAcc->IsDoc()) {
+ if (walkUpAcc->LinkState() & states::LINKED) {
+ if (aIsLink) {
+ *aIsLink = true;
+ }
+ return walkUpAcc;
+ }
+
+ if (nsCoreUtils::HasClickListener(walkUpAcc->GetContent())) {
+ if (aIsOnclick) {
+ *aIsOnclick = true;
+ }
+ return walkUpAcc;
+ }
+
+ if (nsCoreUtils::IsLabelWithControl(walkUpAcc->GetContent())) {
+ if (aIsLabelWithControl) {
+ *aIsLabelWithControl = true;
+ }
+ return walkUpAcc;
+ }
+ }
+ return nullptr;
+}
+
+void
+LinkableAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ aName.Truncate();
+
+ // Action 0 (default action): Jump to link
+ if (aIndex == eAction_Jump) {
+ bool isOnclick, isLink, isLabelWithControl;
+ ActionWalk(&isLink, &isOnclick, &isLabelWithControl);
+ if (isLink) {
+ aName.AssignLiteral("jump");
+ } else if (isOnclick || isLabelWithControl) {
+ aName.AssignLiteral("click");
+ }
+ }
+}
+
+bool
+LinkableAccessible::DoAction(uint8_t aIndex)
+{
+ if (aIndex != eAction_Jump) {
+ return false;
+ }
+
+ if (Accessible* actionAcc = ActionWalk()) {
+ return actionAcc->DoAction(aIndex);
+ }
+
+ return AccessibleWrap::DoAction(aIndex);
+}
+
+KeyBinding
+LinkableAccessible::AccessKey() const
+{
+ if (const Accessible* actionAcc =
+ const_cast<LinkableAccessible*>(this)->ActionWalk()) {
+ return actionAcc->AccessKey();
+ }
+
+ return Accessible::AccessKey();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// LinkableAccessible: HyperLinkAccessible
+
+already_AddRefed<nsIURI>
+LinkableAccessible::AnchorURIAt(uint32_t aAnchorIndex)
+{
+ bool isLink;
+ Accessible* actionAcc = ActionWalk(&isLink);
+ if (isLink) {
+ NS_ASSERTION(actionAcc->IsLink(), "HyperLink isn't implemented.");
+
+ if (actionAcc->IsLink()) {
+ return actionAcc->AnchorURIAt(aAnchorIndex);
+ }
+ }
+
+ return nullptr;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// DummyAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+uint64_t
+DummyAccessible::NativeState()
+{
+ return 0;
+}
+uint64_t
+DummyAccessible::NativeInteractiveState() const
+{
+ return 0;
+}
+
+uint64_t
+DummyAccessible::NativeLinkState() const
+{
+ return 0;
+}
+
+bool
+DummyAccessible::NativelyUnavailable() const
+{
+ return false;
+}
+
+void
+DummyAccessible::ApplyARIAState(uint64_t* aState) const
+{
+}
diff --git a/accessible/generic/BaseAccessibles.h b/accessible/generic/BaseAccessibles.h
new file mode 100644
index 000000000..e4c71c423
--- /dev/null
+++ b/accessible/generic/BaseAccessibles.h
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_BaseAccessibles_h__
+#define mozilla_a11y_BaseAccessibles_h__
+
+#include "AccessibleWrap.h"
+#include "HyperTextAccessibleWrap.h"
+
+class nsIContent;
+
+/**
+ * This file contains a number of classes that are used as base
+ * classes for the different accessibility implementations of
+ * the HTML and XUL widget sets. --jgaunt
+ */
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Leaf version of DOM Accessible -- has no children
+ */
+class LeafAccessible : public AccessibleWrap
+{
+public:
+
+ LeafAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual Accessible* ChildAtPoint(int32_t aX, int32_t aY,
+ EWhichChildAtPoint aWhichChild) override;
+ virtual bool InsertChildAt(uint32_t aIndex, Accessible* aChild) override final;
+ virtual bool RemoveChild(Accessible* aChild) override final;
+
+ virtual bool IsAcceptableChild(nsIContent* aEl) const override;
+
+protected:
+ virtual ~LeafAccessible() {}
+};
+
+/**
+ * Used for text or image accessible nodes contained by link accessibles or
+ * accessibles for nodes with registered click event handler. It knows how to
+ * report the state of the host link (traveled or not) and can activate (click)
+ * the host accessible programmatically.
+ */
+class LinkableAccessible : public AccessibleWrap
+{
+public:
+ enum { eAction_Jump = 0 };
+
+ LinkableAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+ {
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual void Value(nsString& aValue) override;
+ virtual uint64_t NativeLinkState() const override;
+ virtual void TakeFocus() override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t index) override;
+ virtual KeyBinding AccessKey() const override;
+
+ // ActionAccessible helpers
+ Accessible* ActionWalk(bool* aIsLink = nullptr,
+ bool* aIsOnclick = nullptr,
+ bool* aIsLabelWithControl = nullptr);
+ // HyperLinkAccessible
+ virtual already_AddRefed<nsIURI> AnchorURIAt(uint32_t aAnchorIndex) override;
+
+protected:
+ virtual ~LinkableAccessible() {}
+
+};
+
+/**
+ * A simple accessible that gets its enumerated role.
+ */
+template<a11y::role R>
+class EnumRoleAccessible : public AccessibleWrap
+{
+public:
+ EnumRoleAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc) { }
+
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aPtr) override
+ { return Accessible::QueryInterface(aIID, aPtr); }
+
+ // Accessible
+ virtual a11y::role NativeRole() override { return R; }
+
+protected:
+ virtual ~EnumRoleAccessible() { }
+};
+
+
+/**
+ * A wrapper accessible around native accessible to connect it with
+ * crossplatform accessible tree.
+ */
+class DummyAccessible : public AccessibleWrap
+{
+public:
+ explicit DummyAccessible(DocAccessible* aDocument = nullptr) :
+ AccessibleWrap(nullptr, aDocument) { }
+
+ virtual uint64_t NativeState() override final;
+ virtual uint64_t NativeInteractiveState() const override final;
+ virtual uint64_t NativeLinkState() const override final;
+ virtual bool NativelyUnavailable() const override final;
+ virtual void ApplyARIAState(uint64_t* aState) const override final;
+
+protected:
+ virtual ~DummyAccessible() { }
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/generic/DocAccessible-inl.h b/accessible/generic/DocAccessible-inl.h
new file mode 100644
index 000000000..af660ff47
--- /dev/null
+++ b/accessible/generic/DocAccessible-inl.h
@@ -0,0 +1,189 @@
+/* -*- 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_DocAccessible_inl_h_
+#define mozilla_a11y_DocAccessible_inl_h_
+
+#include "DocAccessible.h"
+#include "nsAccessibilityService.h"
+#include "nsAccessiblePivot.h"
+#include "NotificationController.h"
+#include "States.h"
+#include "nsIScrollableFrame.h"
+#include "nsIDocumentInlines.h"
+
+#ifdef A11Y_LOG
+#include "Logging.h"
+#endif
+
+namespace mozilla {
+namespace a11y {
+
+inline Accessible*
+DocAccessible::AccessibleOrTrueContainer(nsINode* aNode) const
+{
+ // HTML comboboxes have no-content list accessible as an intermediate
+ // containing all options.
+ Accessible* container = GetAccessibleOrContainer(aNode);
+ if (container && container->IsHTMLCombobox()) {
+ return container->FirstChild();
+ }
+ return container;
+}
+
+inline nsIAccessiblePivot*
+DocAccessible::VirtualCursor()
+{
+ if (!mVirtualCursor) {
+ mVirtualCursor = new nsAccessiblePivot(this);
+ mVirtualCursor->AddObserver(this);
+ }
+ return mVirtualCursor;
+}
+
+inline void
+DocAccessible::FireDelayedEvent(AccEvent* aEvent)
+{
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocLoad))
+ logging::DocLoadEventFired(aEvent);
+#endif
+
+ mNotificationController->QueueEvent(aEvent);
+}
+
+inline void
+DocAccessible::FireDelayedEvent(uint32_t aEventType, Accessible* aTarget)
+{
+ RefPtr<AccEvent> event = new AccEvent(aEventType, aTarget);
+ FireDelayedEvent(event);
+}
+
+inline void
+DocAccessible::BindChildDocument(DocAccessible* aDocument)
+{
+ mNotificationController->ScheduleChildDocBinding(aDocument);
+}
+
+template<class Class, class Arg>
+inline void
+DocAccessible::HandleNotification(Class* aInstance,
+ typename TNotification<Class, Arg>::Callback aMethod,
+ Arg* aArg)
+{
+ if (mNotificationController) {
+ mNotificationController->HandleNotification<Class, Arg>(aInstance,
+ aMethod, aArg);
+ }
+}
+
+inline void
+DocAccessible::UpdateText(nsIContent* aTextNode)
+{
+ NS_ASSERTION(mNotificationController, "The document was shut down!");
+
+ // Ignore the notification if initial tree construction hasn't been done yet.
+ if (mNotificationController && HasLoadState(eTreeConstructed))
+ mNotificationController->ScheduleTextUpdate(aTextNode);
+}
+
+inline void
+DocAccessible::AddScrollListener()
+{
+ // Delay scroll initializing until the document has a root frame.
+ if (!mPresShell->GetRootFrame())
+ return;
+
+ mDocFlags |= eScrollInitialized;
+ nsIScrollableFrame* sf = mPresShell->GetRootScrollFrameAsScrollable();
+ if (sf) {
+ sf->AddScrollPositionListener(this);
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocCreate))
+ logging::Text("add scroll listener");
+#endif
+ }
+}
+
+inline void
+DocAccessible::RemoveScrollListener()
+{
+ nsIScrollableFrame* sf = mPresShell->GetRootScrollFrameAsScrollable();
+ if (sf)
+ sf->RemoveScrollPositionListener(this);
+}
+
+inline void
+DocAccessible::NotifyOfLoad(uint32_t aLoadEventType)
+{
+ mLoadState |= eDOMLoaded;
+ mLoadEventType = aLoadEventType;
+
+ // If the document is loaded completely then network activity was presumingly
+ // caused by file loading. Fire busy state change event.
+ if (HasLoadState(eCompletelyLoaded) && IsLoadEventTarget()) {
+ RefPtr<AccEvent> stateEvent =
+ new AccStateChangeEvent(this, states::BUSY, false);
+ FireDelayedEvent(stateEvent);
+ }
+}
+
+inline void
+DocAccessible::MaybeNotifyOfValueChange(Accessible* aAccessible)
+{
+ a11y::role role = aAccessible->Role();
+ if (role == roles::ENTRY || role == roles::COMBOBOX)
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE, aAccessible);
+}
+
+inline Accessible*
+DocAccessible::GetAccessibleEvenIfNotInMapOrContainer(nsINode* aNode) const
+{
+ Accessible* acc = GetAccessibleEvenIfNotInMap(aNode);
+ return acc ? acc : GetContainerAccessible(aNode);
+}
+
+inline void
+DocAccessible::CreateSubtree(Accessible* aChild)
+{
+ // If a focused node has been shown then it could mean its frame was recreated
+ // while the node stays focused and we need to fire focus event on
+ // the accessible we just created. If the queue contains a focus event for
+ // this node already then it will be suppressed by this one.
+ Accessible* focusedAcc = nullptr;
+ CacheChildrenInSubtree(aChild, &focusedAcc);
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eVerbose)) {
+ logging::Tree("TREE", "Created subtree", aChild);
+ }
+#endif
+
+ // Fire events for ARIA elements.
+ if (aChild->HasARIARole()) {
+ roles::Role role = aChild->ARIARole();
+ if (role == roles::MENUPOPUP) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START, aChild);
+ }
+ else if (role == roles::ALERT) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_ALERT, aChild);
+ }
+ }
+
+ // XXX: do we really want to send focus to focused DOM node not taking into
+ // account active item?
+ if (focusedAcc) {
+ FocusMgr()->DispatchFocusEvent(this, focusedAcc);
+ SelectionMgr()->
+ SetControlSelectionListener(focusedAcc->GetNode()->AsElement());
+ }
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/generic/DocAccessible.cpp b/accessible/generic/DocAccessible.cpp
new file mode 100644
index 000000000..c89aa189b
--- /dev/null
+++ b/accessible/generic/DocAccessible.cpp
@@ -0,0 +1,2387 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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 "Accessible-inl.h"
+#include "AccIterator.h"
+#include "DocAccessible-inl.h"
+#include "DocAccessibleChild.h"
+#include "HTMLImageMapAccessible.h"
+#include "nsAccCache.h"
+#include "nsAccessiblePivot.h"
+#include "nsAccUtils.h"
+#include "nsEventShell.h"
+#include "nsTextEquivUtils.h"
+#include "Role.h"
+#include "RootAccessible.h"
+#include "TreeWalker.h"
+#include "xpcAccessibleDocument.h"
+
+#include "nsIMutableArray.h"
+#include "nsICommandManager.h"
+#include "nsIDocShell.h"
+#include "nsIDocument.h"
+#include "nsIDOMAttr.h"
+#include "nsIDOMCharacterData.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMXULDocument.h"
+#include "nsIDOMMutationEvent.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDOMXULPopupElement.h"
+#include "nsIEditingSession.h"
+#include "nsIFrame.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsImageFrame.h"
+#include "nsIPersistentProperties2.h"
+#include "nsIPresShell.h"
+#include "nsIServiceManager.h"
+#include "nsViewManager.h"
+#include "nsIScrollableFrame.h"
+#include "nsUnicharUtils.h"
+#include "nsIURI.h"
+#include "nsIWebNavigation.h"
+#include "nsFocusManager.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/dom/DocumentType.h"
+#include "mozilla/dom/Element.h"
+
+#ifdef MOZ_XUL
+#include "nsIXULDocument.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// Static member initialization
+
+static nsIAtom** kRelationAttrs[] =
+{
+ &nsGkAtoms::aria_labelledby,
+ &nsGkAtoms::aria_describedby,
+ &nsGkAtoms::aria_details,
+ &nsGkAtoms::aria_owns,
+ &nsGkAtoms::aria_controls,
+ &nsGkAtoms::aria_flowto,
+ &nsGkAtoms::aria_errormessage,
+ &nsGkAtoms::_for,
+ &nsGkAtoms::control
+};
+
+static const uint32_t kRelationAttrsLen = ArrayLength(kRelationAttrs);
+
+////////////////////////////////////////////////////////////////////////////////
+// Constructor/desctructor
+
+DocAccessible::
+ DocAccessible(nsIDocument* aDocument, nsIPresShell* aPresShell) :
+ // XXX don't pass a document to the Accessible constructor so that we don't
+ // set mDoc until our vtable is fully setup. If we set mDoc before setting
+ // up the vtable we will call Accessible::AddRef() but not the overrides of
+ // it for subclasses. It is important to call those overrides to avoid
+ // confusing leak checking machinary.
+ HyperTextAccessibleWrap(nullptr, nullptr),
+ // XXX aaronl should we use an algorithm for the initial cache size?
+ mAccessibleCache(kDefaultCacheLength),
+ mNodeToAccessibleMap(kDefaultCacheLength),
+ mDocumentNode(aDocument),
+ mScrollPositionChangedTicks(0),
+ mLoadState(eTreeConstructionPending), mDocFlags(0), mLoadEventType(0),
+ mVirtualCursor(nullptr),
+ mPresShell(aPresShell), mIPCDoc(nullptr)
+{
+ mGenericTypes |= eDocument;
+ mStateFlags |= eNotNodeMapEntry;
+ mDoc = this;
+
+ MOZ_ASSERT(mPresShell, "should have been given a pres shell");
+ mPresShell->SetDocAccessible(this);
+
+ // If this is a XUL Document, it should not implement nsHyperText
+ if (mDocumentNode && mDocumentNode->IsXULDocument())
+ mGenericTypes &= ~eHyperText;
+}
+
+DocAccessible::~DocAccessible()
+{
+ NS_ASSERTION(!mPresShell, "LastRelease was never called!?!");
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(DocAccessible)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocAccessible, Accessible)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationController)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVirtualCursor)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildDocuments)
+ for (auto iter = tmp->mDependentIDsHash.Iter(); !iter.Done(); iter.Next()) {
+ AttrRelProviderArray* providers = iter.UserData();
+
+ for (int32_t jdx = providers->Length() - 1; jdx >= 0; jdx--) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
+ cb, "content of dependent ids hash entry of document accessible");
+
+ AttrRelProvider* provider = (*providers)[jdx];
+ cb.NoteXPCOMChild(provider->mContent);
+
+ NS_ASSERTION(provider->mContent->IsInUncomposedDoc(),
+ "Referred content is not in document!");
+ }
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessibleCache)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorJumpElm)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInvalidationList)
+ for (auto it = tmp->mARIAOwnsHash.ConstIter(); !it.Done(); it.Next()) {
+ nsTArray<RefPtr<Accessible> >* ar = it.UserData();
+ for (uint32_t i = 0; i < ar->Length(); i++) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
+ "mARIAOwnsHash entry item");
+ cb.NoteXPCOMChild(ar->ElementAt(i));
+ }
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocAccessible, Accessible)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mNotificationController)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mVirtualCursor)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildDocuments)
+ tmp->mDependentIDsHash.Clear();
+ tmp->mNodeToAccessibleMap.Clear();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessibleCache)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchorJumpElm)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mInvalidationList)
+ tmp->mARIAOwnsHash.Clear();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DocAccessible)
+ NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivotObserver)
+NS_INTERFACE_MAP_END_INHERITING(HyperTextAccessible)
+
+NS_IMPL_ADDREF_INHERITED(DocAccessible, HyperTextAccessible)
+NS_IMPL_RELEASE_INHERITED(DocAccessible, HyperTextAccessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIAccessible
+
+ENameValueFlag
+DocAccessible::Name(nsString& aName)
+{
+ aName.Truncate();
+
+ if (mParent) {
+ mParent->Name(aName); // Allow owning iframe to override the name
+ }
+ if (aName.IsEmpty()) {
+ // Allow name via aria-labelledby or title attribute
+ Accessible::Name(aName);
+ }
+ if (aName.IsEmpty()) {
+ Title(aName); // Try title element
+ }
+ if (aName.IsEmpty()) { // Last resort: use URL
+ URL(aName);
+ }
+
+ return eNameOK;
+}
+
+// Accessible public method
+role
+DocAccessible::NativeRole()
+{
+ nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mDocumentNode);
+ if (docShell) {
+ nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
+ docShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
+ int32_t itemType = docShell->ItemType();
+ if (sameTypeRoot == docShell) {
+ // Root of content or chrome tree
+ if (itemType == nsIDocShellTreeItem::typeChrome)
+ return roles::CHROME_WINDOW;
+
+ if (itemType == nsIDocShellTreeItem::typeContent) {
+#ifdef MOZ_XUL
+ nsCOMPtr<nsIXULDocument> xulDoc(do_QueryInterface(mDocumentNode));
+ if (xulDoc)
+ return roles::APPLICATION;
+#endif
+ return roles::DOCUMENT;
+ }
+ }
+ else if (itemType == nsIDocShellTreeItem::typeContent) {
+ return roles::DOCUMENT;
+ }
+ }
+
+ return roles::PANE; // Fall back;
+}
+
+void
+DocAccessible::Description(nsString& aDescription)
+{
+ if (mParent)
+ mParent->Description(aDescription);
+
+ if (HasOwnContent() && aDescription.IsEmpty()) {
+ nsTextEquivUtils::
+ GetTextEquivFromIDRefs(this, nsGkAtoms::aria_describedby,
+ aDescription);
+ }
+}
+
+// Accessible public method
+uint64_t
+DocAccessible::NativeState()
+{
+ // Document is always focusable.
+ uint64_t state = states::FOCUSABLE; // keep in sync with NativeInteractiveState() impl
+ if (FocusMgr()->IsFocused(this))
+ state |= states::FOCUSED;
+
+ // Expose stale state until the document is ready (DOM is loaded and tree is
+ // constructed).
+ if (!HasLoadState(eReady))
+ state |= states::STALE;
+
+ // Expose state busy until the document and all its subdocuments is completely
+ // loaded.
+ if (!HasLoadState(eCompletelyLoaded))
+ state |= states::BUSY;
+
+ nsIFrame* frame = GetFrame();
+ if (!frame ||
+ !frame->IsVisibleConsideringAncestors(nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) {
+ state |= states::INVISIBLE | states::OFFSCREEN;
+ }
+
+ nsCOMPtr<nsIEditor> editor = GetEditor();
+ state |= editor ? states::EDITABLE : states::READONLY;
+
+ return state;
+}
+
+uint64_t
+DocAccessible::NativeInteractiveState() const
+{
+ // Document is always focusable.
+ return states::FOCUSABLE;
+}
+
+bool
+DocAccessible::NativelyUnavailable() const
+{
+ return false;
+}
+
+// Accessible public method
+void
+DocAccessible::ApplyARIAState(uint64_t* aState) const
+{
+ // Grab states from content element.
+ if (mContent)
+ Accessible::ApplyARIAState(aState);
+
+ // Allow iframe/frame etc. to have final state override via ARIA.
+ if (mParent)
+ mParent->ApplyARIAState(aState);
+}
+
+already_AddRefed<nsIPersistentProperties>
+DocAccessible::Attributes()
+{
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ HyperTextAccessibleWrap::Attributes();
+
+ // No attributes if document is not attached to the tree or if it's a root
+ // document.
+ if (!mParent || IsRoot())
+ return attributes.forget();
+
+ // Override ARIA object attributes from outerdoc.
+ aria::AttrIterator attribIter(mParent->GetContent());
+ nsAutoString name, value, unused;
+ while(attribIter.Next(name, value))
+ attributes->SetStringProperty(NS_ConvertUTF16toUTF8(name), value, unused);
+
+ return attributes.forget();
+}
+
+Accessible*
+DocAccessible::FocusedChild()
+{
+ // Return an accessible for the current global focus, which does not have to
+ // be contained within the current document.
+ return FocusMgr()->FocusedAccessible();
+}
+
+void
+DocAccessible::TakeFocus()
+{
+ // Focus the document.
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ nsCOMPtr<nsIDOMElement> newFocus;
+ fm->MoveFocus(mDocumentNode->GetWindow(), nullptr,
+ nsFocusManager::MOVEFOCUS_ROOT, 0, getter_AddRefs(newFocus));
+}
+
+// HyperTextAccessible method
+already_AddRefed<nsIEditor>
+DocAccessible::GetEditor() const
+{
+ // Check if document is editable (designMode="on" case). Otherwise check if
+ // the html:body (for HTML document case) or document element is editable.
+ if (!mDocumentNode->HasFlag(NODE_IS_EDITABLE) &&
+ (!mContent || !mContent->HasFlag(NODE_IS_EDITABLE)))
+ return nullptr;
+
+ nsCOMPtr<nsIDocShell> docShell = mDocumentNode->GetDocShell();
+ if (!docShell) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIEditingSession> editingSession;
+ docShell->GetEditingSession(getter_AddRefs(editingSession));
+ if (!editingSession)
+ return nullptr; // No editing session interface
+
+ nsCOMPtr<nsIEditor> editor;
+ editingSession->GetEditorForWindow(mDocumentNode->GetWindow(), getter_AddRefs(editor));
+ if (!editor)
+ return nullptr;
+
+ bool isEditable = false;
+ editor->GetIsDocumentEditable(&isEditable);
+ if (isEditable)
+ return editor.forget();
+
+ return nullptr;
+}
+
+// DocAccessible public method
+
+void
+DocAccessible::URL(nsAString& aURL) const
+{
+ nsCOMPtr<nsISupports> container = mDocumentNode->GetContainer();
+ nsCOMPtr<nsIWebNavigation> webNav(do_GetInterface(container));
+ nsAutoCString theURL;
+ if (webNav) {
+ nsCOMPtr<nsIURI> pURI;
+ webNav->GetCurrentURI(getter_AddRefs(pURI));
+ if (pURI)
+ pURI->GetSpec(theURL);
+ }
+ CopyUTF8toUTF16(theURL, aURL);
+}
+
+void
+DocAccessible::DocType(nsAString& aType) const
+{
+#ifdef MOZ_XUL
+ nsCOMPtr<nsIXULDocument> xulDoc(do_QueryInterface(mDocumentNode));
+ if (xulDoc) {
+ aType.AssignLiteral("window"); // doctype not implemented for XUL at time of writing - causes assertion
+ return;
+ }
+#endif
+ dom::DocumentType* docType = mDocumentNode->GetDoctype();
+ if (docType)
+ docType->GetPublicId(aType);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible
+
+void
+DocAccessible::Init()
+{
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocCreate))
+ logging::DocCreate("document initialize", mDocumentNode, this);
+#endif
+
+ // Initialize notification controller.
+ mNotificationController = new NotificationController(this, mPresShell);
+
+ // Mark the document accessible as loaded if its DOM document was loaded at
+ // this point (this can happen because a11y is started late or DOM document
+ // having no container was loaded.
+ if (mDocumentNode->GetReadyStateEnum() == nsIDocument::READYSTATE_COMPLETE)
+ mLoadState |= eDOMLoaded;
+
+ AddEventListeners();
+}
+
+void
+DocAccessible::Shutdown()
+{
+ if (!mPresShell) // already shutdown
+ return;
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocDestroy))
+ logging::DocDestroy("document shutdown", mDocumentNode, this);
+#endif
+
+ // Mark the document as shutdown before AT is notified about the document
+ // removal from its container (valid for root documents on ATK and due to
+ // some reason for MSAA, refer to bug 757392 for details).
+ mStateFlags |= eIsDefunct;
+
+ if (mNotificationController) {
+ mNotificationController->Shutdown();
+ mNotificationController = nullptr;
+ }
+
+ RemoveEventListeners();
+
+ nsCOMPtr<nsIDocument> kungFuDeathGripDoc = mDocumentNode;
+ mDocumentNode = nullptr;
+
+ if (mParent) {
+ DocAccessible* parentDocument = mParent->Document();
+ if (parentDocument)
+ parentDocument->RemoveChildDocument(this);
+
+ mParent->RemoveChild(this);
+ }
+
+ // Walk the array backwards because child documents remove themselves from the
+ // array as they are shutdown.
+ int32_t childDocCount = mChildDocuments.Length();
+ for (int32_t idx = childDocCount - 1; idx >= 0; idx--)
+ mChildDocuments[idx]->Shutdown();
+
+ mChildDocuments.Clear();
+
+ // XXX thinking about ordering?
+ if (mIPCDoc) {
+ MOZ_ASSERT(IPCAccessibilityActive());
+ mIPCDoc->Shutdown();
+ MOZ_ASSERT(!mIPCDoc);
+ }
+
+ if (mVirtualCursor) {
+ mVirtualCursor->RemoveObserver(this);
+ mVirtualCursor = nullptr;
+ }
+
+ mPresShell->SetDocAccessible(nullptr);
+ mPresShell = nullptr; // Avoid reentrancy
+
+ mDependentIDsHash.Clear();
+ mNodeToAccessibleMap.Clear();
+
+ for (auto iter = mAccessibleCache.Iter(); !iter.Done(); iter.Next()) {
+ Accessible* accessible = iter.Data();
+ MOZ_ASSERT(accessible);
+ if (accessible && !accessible->IsDefunct()) {
+ // Unlink parent to avoid its cleaning overhead in shutdown.
+ accessible->mParent = nullptr;
+ accessible->Shutdown();
+ }
+ iter.Remove();
+ }
+
+ HyperTextAccessibleWrap::Shutdown();
+
+ GetAccService()->NotifyOfDocumentShutdown(this, kungFuDeathGripDoc);
+}
+
+nsIFrame*
+DocAccessible::GetFrame() const
+{
+ nsIFrame* root = nullptr;
+ if (mPresShell)
+ root = mPresShell->GetRootFrame();
+
+ return root;
+}
+
+// DocAccessible protected member
+nsRect
+DocAccessible::RelativeBounds(nsIFrame** aRelativeFrame) const
+{
+ *aRelativeFrame = GetFrame();
+
+ nsIDocument *document = mDocumentNode;
+ nsIDocument *parentDoc = nullptr;
+
+ nsRect bounds;
+ while (document) {
+ nsIPresShell *presShell = document->GetShell();
+ if (!presShell)
+ return nsRect();
+
+ nsRect scrollPort;
+ nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollableExternal();
+ if (sf) {
+ scrollPort = sf->GetScrollPortRect();
+ } else {
+ nsIFrame* rootFrame = presShell->GetRootFrame();
+ if (!rootFrame)
+ return nsRect();
+
+ scrollPort = rootFrame->GetRect();
+ }
+
+ if (parentDoc) { // After first time thru loop
+ // XXXroc bogus code! scrollPort is relative to the viewport of
+ // this document, but we're intersecting rectangles derived from
+ // multiple documents and assuming they're all in the same coordinate
+ // system. See bug 514117.
+ bounds.IntersectRect(scrollPort, bounds);
+ }
+ else { // First time through loop
+ bounds = scrollPort;
+ }
+
+ document = parentDoc = document->GetParentDocument();
+ }
+
+ return bounds;
+}
+
+// DocAccessible protected member
+nsresult
+DocAccessible::AddEventListeners()
+{
+ nsCOMPtr<nsIDocShell> docShell(mDocumentNode->GetDocShell());
+
+ // We want to add a command observer only if the document is content and has
+ // an editor.
+ if (docShell->ItemType() == nsIDocShellTreeItem::typeContent) {
+ nsCOMPtr<nsICommandManager> commandManager = docShell->GetCommandManager();
+ if (commandManager)
+ commandManager->AddCommandObserver(this, "obs_documentCreated");
+ }
+
+ SelectionMgr()->AddDocSelectionListener(mPresShell);
+
+ // Add document observer.
+ mDocumentNode->AddObserver(this);
+ return NS_OK;
+}
+
+// DocAccessible protected member
+nsresult
+DocAccessible::RemoveEventListeners()
+{
+ // Remove listeners associated with content documents
+ // Remove scroll position listener
+ RemoveScrollListener();
+
+ NS_ASSERTION(mDocumentNode, "No document during removal of listeners.");
+
+ if (mDocumentNode) {
+ mDocumentNode->RemoveObserver(this);
+
+ nsCOMPtr<nsIDocShell> docShell(mDocumentNode->GetDocShell());
+ NS_ASSERTION(docShell, "doc should support nsIDocShellTreeItem.");
+
+ if (docShell) {
+ if (docShell->ItemType() == nsIDocShellTreeItem::typeContent) {
+ nsCOMPtr<nsICommandManager> commandManager = docShell->GetCommandManager();
+ if (commandManager) {
+ commandManager->RemoveCommandObserver(this, "obs_documentCreated");
+ }
+ }
+ }
+ }
+
+ if (mScrollWatchTimer) {
+ mScrollWatchTimer->Cancel();
+ mScrollWatchTimer = nullptr;
+ NS_RELEASE_THIS(); // Kung fu death grip
+ }
+
+ SelectionMgr()->RemoveDocSelectionListener(mPresShell);
+ return NS_OK;
+}
+
+void
+DocAccessible::ScrollTimerCallback(nsITimer* aTimer, void* aClosure)
+{
+ DocAccessible* docAcc = reinterpret_cast<DocAccessible*>(aClosure);
+
+ if (docAcc && docAcc->mScrollPositionChangedTicks &&
+ ++docAcc->mScrollPositionChangedTicks > 2) {
+ // Whenever scroll position changes, mScrollPositionChangeTicks gets reset to 1
+ // We only want to fire accessibilty scroll event when scrolling stops or pauses
+ // Therefore, we wait for no scroll events to occur between 2 ticks of this timer
+ // That indicates a pause in scrolling, so we fire the accessibilty scroll event
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_END, docAcc);
+
+ docAcc->mScrollPositionChangedTicks = 0;
+ if (docAcc->mScrollWatchTimer) {
+ docAcc->mScrollWatchTimer->Cancel();
+ docAcc->mScrollWatchTimer = nullptr;
+ NS_RELEASE(docAcc); // Release kung fu death grip
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIScrollPositionListener
+
+void
+DocAccessible::ScrollPositionDidChange(nscoord aX, nscoord aY)
+{
+ // Start new timer, if the timer cycles at least 1 full cycle without more scroll position changes,
+ // then the ::Notify() method will fire the accessibility event for scroll position changes
+ const uint32_t kScrollPosCheckWait = 50;
+ if (mScrollWatchTimer) {
+ mScrollWatchTimer->SetDelay(kScrollPosCheckWait); // Create new timer, to avoid leaks
+ }
+ else {
+ mScrollWatchTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (mScrollWatchTimer) {
+ NS_ADDREF_THIS(); // Kung fu death grip
+ mScrollWatchTimer->InitWithFuncCallback(ScrollTimerCallback, this,
+ kScrollPosCheckWait,
+ nsITimer::TYPE_REPEATING_SLACK);
+ }
+ }
+ mScrollPositionChangedTicks = 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIObserver
+
+NS_IMETHODIMP
+DocAccessible::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ if (!nsCRT::strcmp(aTopic,"obs_documentCreated")) {
+ // State editable will now be set, readonly is now clear
+ // Normally we only fire delayed events created from the node, not an
+ // accessible object. See the AccStateChangeEvent constructor for details
+ // about this exceptional case.
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(this, states::EDITABLE, true);
+ FireDelayedEvent(event);
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIAccessiblePivotObserver
+
+NS_IMETHODIMP
+DocAccessible::OnPivotChanged(nsIAccessiblePivot* aPivot,
+ nsIAccessible* aOldAccessible,
+ int32_t aOldStart, int32_t aOldEnd,
+ PivotMoveReason aReason,
+ bool aIsFromUserInput)
+{
+ RefPtr<AccEvent> event =
+ new AccVCChangeEvent(
+ this, (aOldAccessible ? aOldAccessible->ToInternalAccessible() : nullptr),
+ aOldStart, aOldEnd, aReason,
+ aIsFromUserInput ? eFromUserInput : eNoUserInput);
+ nsEventShell::FireEvent(event);
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIDocumentObserver
+
+NS_IMPL_NSIDOCUMENTOBSERVER_CORE_STUB(DocAccessible)
+NS_IMPL_NSIDOCUMENTOBSERVER_LOAD_STUB(DocAccessible)
+NS_IMPL_NSIDOCUMENTOBSERVER_STYLE_STUB(DocAccessible)
+
+void
+DocAccessible::AttributeWillChange(nsIDocument* aDocument,
+ dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aNewValue)
+{
+ Accessible* accessible = GetAccessible(aElement);
+ if (!accessible) {
+ if (aElement != mContent)
+ return;
+
+ accessible = this;
+ }
+
+ // Update dependent IDs cache. Take care of elements that are accessible
+ // because dependent IDs cache doesn't contain IDs from non accessible
+ // elements.
+ if (aModType != nsIDOMMutationEvent::ADDITION)
+ RemoveDependentIDsFor(accessible, aAttribute);
+
+ if (aAttribute == nsGkAtoms::id) {
+ RelocateARIAOwnedIfNeeded(aElement);
+ }
+
+ // Store the ARIA attribute old value so that it can be used after
+ // attribute change. Note, we assume there's no nested ARIA attribute
+ // changes. If this happens then we should end up with keeping a stack of
+ // old values.
+
+ // XXX TODO: bugs 472142, 472143.
+ // Here we will want to cache whatever attribute values we are interested
+ // in, such as the existence of aria-pressed for button (so we know if we
+ // need to newly expose it as a toggle button) etc.
+ if (aAttribute == nsGkAtoms::aria_checked ||
+ aAttribute == nsGkAtoms::aria_pressed) {
+ mARIAAttrOldValue = (aModType != nsIDOMMutationEvent::ADDITION) ?
+ nsAccUtils::GetARIAToken(aElement, aAttribute) : nullptr;
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::aria_disabled ||
+ aAttribute == nsGkAtoms::disabled)
+ mStateBitWasOn = accessible->Unavailable();
+}
+
+void
+DocAccessible::NativeAnonymousChildListChange(nsIDocument* aDocument,
+ nsIContent* aContent,
+ bool aIsRemove)
+{
+}
+
+void
+DocAccessible::AttributeChanged(nsIDocument* aDocument,
+ dom::Element* aElement,
+ int32_t aNameSpaceID, nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ NS_ASSERTION(!IsDefunct(),
+ "Attribute changed called on defunct document accessible!");
+
+ // Proceed even if the element is not accessible because element may become
+ // accessible if it gets certain attribute.
+ if (UpdateAccessibleOnAttrChange(aElement, aAttribute))
+ return;
+
+ // Ignore attribute change if the element doesn't have an accessible (at all
+ // or still) iff the element is not a root content of this document accessible
+ // (which is treated as attribute change on this document accessible).
+ // Note: we don't bail if all the content hasn't finished loading because
+ // these attributes are changing for a loaded part of the content.
+ Accessible* accessible = GetAccessible(aElement);
+ if (!accessible) {
+ if (mContent != aElement)
+ return;
+
+ accessible = this;
+ }
+
+ MOZ_ASSERT(accessible->IsBoundToParent() || accessible->IsDoc(),
+ "DOM attribute change on an accessible detached from the tree");
+
+ // Fire accessible events iff there's an accessible, otherwise we consider
+ // the accessible state wasn't changed, i.e. its state is initial state.
+ AttributeChangedImpl(accessible, aNameSpaceID, aAttribute);
+
+ // Update dependent IDs cache. Take care of accessible elements because no
+ // accessible element means either the element is not accessible at all or
+ // its accessible will be created later. It doesn't make sense to keep
+ // dependent IDs for non accessible elements. For the second case we'll update
+ // dependent IDs cache when its accessible is created.
+ if (aModType == nsIDOMMutationEvent::MODIFICATION ||
+ aModType == nsIDOMMutationEvent::ADDITION) {
+ AddDependentIDsFor(accessible, aAttribute);
+ }
+}
+
+// DocAccessible protected member
+void
+DocAccessible::AttributeChangedImpl(Accessible* aAccessible,
+ int32_t aNameSpaceID, nsIAtom* aAttribute)
+{
+ // Fire accessible event after short timer, because we need to wait for
+ // DOM attribute & resulting layout to actually change. Otherwise,
+ // assistive technology will retrieve the wrong state/value/selection info.
+
+ // XXX todo
+ // We still need to handle special HTML cases here
+ // For example, if an <img>'s usemap attribute is modified
+ // Otherwise it may just be a state change, for example an object changing
+ // its visibility
+ //
+ // XXX todo: report aria state changes for "undefined" literal value changes
+ // filed as bug 472142
+ //
+ // XXX todo: invalidate accessible when aria state changes affect exposed role
+ // filed as bug 472143
+
+ // Universal boolean properties that don't require a role. Fire the state
+ // change when disabled or aria-disabled attribute is set.
+ // Note. Checking the XUL or HTML namespace would not seem to gain us
+ // anything, because disabled attribute really is going to mean the same
+ // thing in any namespace.
+ // Note. We use the attribute instead of the disabled state bit because
+ // ARIA's aria-disabled does not affect the disabled state bit.
+ if (aAttribute == nsGkAtoms::disabled ||
+ aAttribute == nsGkAtoms::aria_disabled) {
+ // Do nothing if state wasn't changed (like @aria-disabled was removed but
+ // @disabled is still presented).
+ if (aAccessible->Unavailable() == mStateBitWasOn)
+ return;
+
+ RefPtr<AccEvent> enabledChangeEvent =
+ new AccStateChangeEvent(aAccessible, states::ENABLED, mStateBitWasOn);
+ FireDelayedEvent(enabledChangeEvent);
+
+ RefPtr<AccEvent> sensitiveChangeEvent =
+ new AccStateChangeEvent(aAccessible, states::SENSITIVE, mStateBitWasOn);
+ FireDelayedEvent(sensitiveChangeEvent);
+ return;
+ }
+
+ // Check for namespaced ARIA attribute
+ if (aNameSpaceID == kNameSpaceID_None) {
+ // Check for hyphenated aria-foo property?
+ if (StringBeginsWith(nsDependentAtomString(aAttribute),
+ NS_LITERAL_STRING("aria-"))) {
+ ARIAAttributeChanged(aAccessible, aAttribute);
+ }
+ }
+
+ // Fire name change and description change events. XXX: it's not complete and
+ // dupes the code logic of accessible name and description calculation, we do
+ // that for performance reasons.
+ if (aAttribute == nsGkAtoms::aria_label) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::aria_describedby) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, aAccessible);
+ return;
+ }
+
+ nsIContent* elm = aAccessible->GetContent();
+ if (aAttribute == nsGkAtoms::aria_labelledby &&
+ !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label)) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::alt &&
+ !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
+ !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby)) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::title) {
+ if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_label) &&
+ !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_labelledby) &&
+ !elm->HasAttr(kNameSpaceID_None, nsGkAtoms::alt)) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, aAccessible);
+ return;
+ }
+
+ if (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_describedby))
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, aAccessible);
+
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::aria_busy) {
+ bool isOn = elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true,
+ eCaseMatters);
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(aAccessible, states::BUSY, isOn);
+ FireDelayedEvent(event);
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::id) {
+ RelocateARIAOwnedIfNeeded(elm);
+ }
+
+ // ARIA or XUL selection
+ if ((aAccessible->GetContent()->IsXULElement() &&
+ aAttribute == nsGkAtoms::selected) ||
+ aAttribute == nsGkAtoms::aria_selected) {
+ Accessible* widget =
+ nsAccUtils::GetSelectableContainer(aAccessible, aAccessible->State());
+ if (widget) {
+ AccSelChangeEvent::SelChangeType selChangeType =
+ elm->AttrValueIs(aNameSpaceID, aAttribute, nsGkAtoms::_true, eCaseMatters) ?
+ AccSelChangeEvent::eSelectionAdd : AccSelChangeEvent::eSelectionRemove;
+
+ RefPtr<AccEvent> event =
+ new AccSelChangeEvent(widget, aAccessible, selChangeType);
+ FireDelayedEvent(event);
+ }
+
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::contenteditable) {
+ RefPtr<AccEvent> editableChangeEvent =
+ new AccStateChangeEvent(aAccessible, states::EDITABLE);
+ FireDelayedEvent(editableChangeEvent);
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::value) {
+ if (aAccessible->IsProgress())
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible);
+ }
+}
+
+// DocAccessible protected member
+void
+DocAccessible::ARIAAttributeChanged(Accessible* aAccessible, nsIAtom* aAttribute)
+{
+ // Note: For universal/global ARIA states and properties we don't care if
+ // there is an ARIA role present or not.
+
+ if (aAttribute == nsGkAtoms::aria_required) {
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(aAccessible, states::REQUIRED);
+ FireDelayedEvent(event);
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::aria_invalid) {
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(aAccessible, states::INVALID);
+ FireDelayedEvent(event);
+ return;
+ }
+
+ // The activedescendant universal property redirects accessible focus events
+ // to the element with the id that activedescendant points to. Make sure
+ // the tree up to date before processing.
+ if (aAttribute == nsGkAtoms::aria_activedescendant) {
+ mNotificationController->HandleNotification<DocAccessible, Accessible>
+ (this, &DocAccessible::ARIAActiveDescendantChanged, aAccessible);
+
+ return;
+ }
+
+ // We treat aria-expanded as a global ARIA state for historical reasons
+ if (aAttribute == nsGkAtoms::aria_expanded) {
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(aAccessible, states::EXPANDED);
+ FireDelayedEvent(event);
+ return;
+ }
+
+ // For aria attributes like drag and drop changes we fire a generic attribute
+ // change event; at least until native API comes up with a more meaningful event.
+ uint8_t attrFlags = aria::AttrCharacteristicsFor(aAttribute);
+ if (!(attrFlags & ATTR_BYPASSOBJ)) {
+ RefPtr<AccEvent> event =
+ new AccObjectAttrChangedEvent(aAccessible, aAttribute);
+ FireDelayedEvent(event);
+ }
+
+ nsIContent* elm = aAccessible->GetContent();
+
+ // Update aria-hidden flag for the whole subtree iff aria-hidden is changed
+ // on the root, i.e. ignore any affiliated aria-hidden changes in the subtree
+ // of top aria-hidden.
+ if (aAttribute == nsGkAtoms::aria_hidden) {
+ bool isDefined = aria::HasDefinedARIAHidden(elm);
+ if (isDefined != aAccessible->IsARIAHidden() &&
+ (!aAccessible->Parent() || !aAccessible->Parent()->IsARIAHidden())) {
+ aAccessible->SetARIAHidden(isDefined);
+
+ RefPtr<AccEvent> event =
+ new AccObjectAttrChangedEvent(aAccessible, aAttribute);
+ FireDelayedEvent(event);
+ }
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::aria_checked ||
+ (aAccessible->IsButton() &&
+ aAttribute == nsGkAtoms::aria_pressed)) {
+ const uint64_t kState = (aAttribute == nsGkAtoms::aria_checked) ?
+ states::CHECKED : states::PRESSED;
+ RefPtr<AccEvent> event = new AccStateChangeEvent(aAccessible, kState);
+ FireDelayedEvent(event);
+
+ bool wasMixed = (mARIAAttrOldValue == nsGkAtoms::mixed);
+ bool isMixed = elm->AttrValueIs(kNameSpaceID_None, aAttribute,
+ nsGkAtoms::mixed, eCaseMatters);
+ if (isMixed != wasMixed) {
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(aAccessible, states::MIXED, isMixed);
+ FireDelayedEvent(event);
+ }
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::aria_readonly) {
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(aAccessible, states::READONLY);
+ FireDelayedEvent(event);
+ return;
+ }
+
+ // Fire text value change event whenever aria-valuetext is changed.
+ if (aAttribute == nsGkAtoms::aria_valuetext) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE, aAccessible);
+ return;
+ }
+
+ // Fire numeric value change event when aria-valuenow is changed and
+ // aria-valuetext is empty
+ if (aAttribute == nsGkAtoms::aria_valuenow &&
+ (!elm->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext) ||
+ elm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_valuetext,
+ nsGkAtoms::_empty, eCaseMatters))) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible);
+ return;
+ }
+
+ if (aAttribute == nsGkAtoms::aria_owns) {
+ mNotificationController->ScheduleRelocation(aAccessible);
+ }
+}
+
+void
+DocAccessible::ARIAActiveDescendantChanged(Accessible* aAccessible)
+{
+ nsIContent* elm = aAccessible->GetContent();
+ if (elm && aAccessible->IsActiveWidget()) {
+ nsAutoString id;
+ if (elm->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant, id)) {
+ dom::Element* activeDescendantElm = elm->OwnerDoc()->GetElementById(id);
+ if (activeDescendantElm) {
+ Accessible* activeDescendant = GetAccessible(activeDescendantElm);
+ if (activeDescendant) {
+ FocusMgr()->ActiveItemChanged(activeDescendant, false);
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::ActiveItemChangeCausedBy("ARIA activedescedant changed",
+ activeDescendant);
+#endif
+ }
+ }
+ }
+ }
+}
+
+void
+DocAccessible::ContentAppended(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t /* unused */)
+{
+}
+
+void
+DocAccessible::ContentStateChanged(nsIDocument* aDocument,
+ nsIContent* aContent,
+ EventStates aStateMask)
+{
+ Accessible* accessible = GetAccessible(aContent);
+ if (!accessible)
+ return;
+
+ if (aStateMask.HasState(NS_EVENT_STATE_CHECKED)) {
+ Accessible* widget = accessible->ContainerWidget();
+ if (widget && widget->IsSelect()) {
+ AccSelChangeEvent::SelChangeType selChangeType =
+ aContent->AsElement()->State().HasState(NS_EVENT_STATE_CHECKED) ?
+ AccSelChangeEvent::eSelectionAdd : AccSelChangeEvent::eSelectionRemove;
+ RefPtr<AccEvent> event =
+ new AccSelChangeEvent(widget, accessible, selChangeType);
+ FireDelayedEvent(event);
+ return;
+ }
+
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(accessible, states::CHECKED,
+ aContent->AsElement()->State().HasState(NS_EVENT_STATE_CHECKED));
+ FireDelayedEvent(event);
+ }
+
+ if (aStateMask.HasState(NS_EVENT_STATE_INVALID)) {
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(accessible, states::INVALID, true);
+ FireDelayedEvent(event);
+ }
+
+ if (aStateMask.HasState(NS_EVENT_STATE_VISITED)) {
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(accessible, states::TRAVERSED, true);
+ FireDelayedEvent(event);
+ }
+}
+
+void
+DocAccessible::DocumentStatesChanged(nsIDocument* aDocument,
+ EventStates aStateMask)
+{
+}
+
+void
+DocAccessible::CharacterDataWillChange(nsIDocument* aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+}
+
+void
+DocAccessible::CharacterDataChanged(nsIDocument* aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+}
+
+void
+DocAccessible::ContentInserted(nsIDocument* aDocument, nsIContent* aContainer,
+ nsIContent* aChild, int32_t /* unused */)
+{
+}
+
+void
+DocAccessible::ContentRemoved(nsIDocument* aDocument,
+ nsIContent* aContainerNode,
+ nsIContent* aChildNode, int32_t /* unused */,
+ nsIContent* aPreviousSiblingNode)
+{
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eTree)) {
+ logging::MsgBegin("TREE", "DOM content removed; doc: %p", this);
+ logging::Node("container node", aContainerNode);
+ logging::Node("content node", aChildNode);
+ logging::MsgEnd();
+ }
+#endif
+ // This one and content removal notification from layout may result in
+ // double processing of same subtrees. If it pops up in profiling, then
+ // consider reusing a document node cache to reject these notifications early.
+ Accessible* container = GetAccessibleOrContainer(aContainerNode);
+ if (container) {
+ UpdateTreeOnRemoval(container, aChildNode);
+ }
+}
+
+void
+DocAccessible::ParentChainChanged(nsIContent* aContent)
+{
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible
+
+#ifdef A11Y_LOG
+nsresult
+DocAccessible::HandleAccEvent(AccEvent* aEvent)
+{
+ if (logging::IsEnabled(logging::eDocLoad))
+ logging::DocLoadEventHandled(aEvent);
+
+ return HyperTextAccessible::HandleAccEvent(aEvent);
+}
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// Public members
+
+void*
+DocAccessible::GetNativeWindow() const
+{
+ if (!mPresShell)
+ return nullptr;
+
+ nsViewManager* vm = mPresShell->GetViewManager();
+ if (!vm)
+ return nullptr;
+
+ nsCOMPtr<nsIWidget> widget;
+ vm->GetRootWidget(getter_AddRefs(widget));
+ if (widget)
+ return widget->GetNativeData(NS_NATIVE_WINDOW);
+
+ return nullptr;
+}
+
+Accessible*
+DocAccessible::GetAccessibleByUniqueIDInSubtree(void* aUniqueID)
+{
+ Accessible* child = GetAccessibleByUniqueID(aUniqueID);
+ if (child)
+ return child;
+
+ uint32_t childDocCount = mChildDocuments.Length();
+ for (uint32_t childDocIdx= 0; childDocIdx < childDocCount; childDocIdx++) {
+ DocAccessible* childDocument = mChildDocuments.ElementAt(childDocIdx);
+ child = childDocument->GetAccessibleByUniqueIDInSubtree(aUniqueID);
+ if (child)
+ return child;
+ }
+
+ return nullptr;
+}
+
+Accessible*
+DocAccessible::GetAccessibleOrContainer(nsINode* aNode) const
+{
+ if (!aNode || !aNode->GetComposedDoc())
+ return nullptr;
+
+ nsINode* currNode = aNode;
+ Accessible* accessible = nullptr;
+ while (!(accessible = GetAccessible(currNode))) {
+ nsINode* parent = nullptr;
+
+ // If this is a content node, try to get a flattened parent content node.
+ // This will smartly skip from the shadow root to the host element,
+ // over parentless document fragment
+ if (currNode->IsContent())
+ parent = currNode->AsContent()->GetFlattenedTreeParent();
+
+ // Fallback to just get parent node, in case there is no parent content
+ // node. Or current node is not a content node.
+ if (!parent)
+ parent = currNode->GetParentNode();
+
+ if (!(currNode = parent)) break;
+ }
+
+ return accessible;
+}
+
+Accessible*
+DocAccessible::GetAccessibleOrDescendant(nsINode* aNode) const
+{
+ Accessible* acc = GetAccessible(aNode);
+ if (acc)
+ return acc;
+
+ acc = GetContainerAccessible(aNode);
+ if (acc) {
+ uint32_t childCnt = acc->ChildCount();
+ for (uint32_t idx = 0; idx < childCnt; idx++) {
+ Accessible* child = acc->GetChildAt(idx);
+ for (nsIContent* elm = child->GetContent();
+ elm && elm != acc->GetContent();
+ elm = elm->GetFlattenedTreeParent()) {
+ if (elm == aNode)
+ return child;
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+void
+DocAccessible::BindToDocument(Accessible* aAccessible,
+ const nsRoleMapEntry* aRoleMapEntry)
+{
+ // Put into DOM node cache.
+ if (aAccessible->IsNodeMapEntry())
+ mNodeToAccessibleMap.Put(aAccessible->GetNode(), aAccessible);
+
+ // Put into unique ID cache.
+ mAccessibleCache.Put(aAccessible->UniqueID(), aAccessible);
+
+ aAccessible->SetRoleMapEntry(aRoleMapEntry);
+
+ AddDependentIDsFor(aAccessible);
+
+ if (aAccessible->HasOwnContent()) {
+ nsIContent* el = aAccessible->GetContent();
+ if (el->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_owns)) {
+ mNotificationController->ScheduleRelocation(aAccessible);
+ }
+ }
+}
+
+void
+DocAccessible::UnbindFromDocument(Accessible* aAccessible)
+{
+ NS_ASSERTION(mAccessibleCache.GetWeak(aAccessible->UniqueID()),
+ "Unbinding the unbound accessible!");
+
+ // Fire focus event on accessible having DOM focus if active item was removed
+ // from the tree.
+ if (FocusMgr()->IsActiveItem(aAccessible)) {
+ FocusMgr()->ActiveItemChanged(nullptr);
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::ActiveItemChangeCausedBy("tree shutdown", aAccessible);
+#endif
+ }
+
+ // Remove an accessible from node-to-accessible map if it exists there.
+ if (aAccessible->IsNodeMapEntry() &&
+ mNodeToAccessibleMap.Get(aAccessible->GetNode()) == aAccessible)
+ mNodeToAccessibleMap.Remove(aAccessible->GetNode());
+
+ aAccessible->mStateFlags |= eIsNotInDocument;
+
+ // Update XPCOM part.
+ xpcAccessibleDocument* xpcDoc = GetAccService()->GetCachedXPCDocument(this);
+ if (xpcDoc)
+ xpcDoc->NotifyOfShutdown(aAccessible);
+
+ void* uniqueID = aAccessible->UniqueID();
+
+ NS_ASSERTION(!aAccessible->IsDefunct(), "Shutdown the shutdown accessible!");
+ aAccessible->Shutdown();
+
+ mAccessibleCache.Remove(uniqueID);
+}
+
+void
+DocAccessible::ContentInserted(nsIContent* aContainerNode,
+ nsIContent* aStartChildNode,
+ nsIContent* aEndChildNode)
+{
+ // Ignore content insertions until we constructed accessible tree. Otherwise
+ // schedule tree update on content insertion after layout.
+ if (mNotificationController && HasLoadState(eTreeConstructed)) {
+ // Update the whole tree of this document accessible when the container is
+ // null (document element is inserted or removed).
+ Accessible* container = aContainerNode ?
+ AccessibleOrTrueContainer(aContainerNode) : this;
+ if (container) {
+ // Ignore notification if the container node is no longer in the DOM tree.
+ mNotificationController->ScheduleContentInsertion(container,
+ aStartChildNode,
+ aEndChildNode);
+ }
+ }
+}
+
+void
+DocAccessible::RecreateAccessible(nsIContent* aContent)
+{
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eTree)) {
+ logging::MsgBegin("TREE", "accessible recreated");
+ logging::Node("content", aContent);
+ logging::MsgEnd();
+ }
+#endif
+
+ // XXX: we shouldn't recreate whole accessible subtree, instead we should
+ // subclass hide and show events to handle them separately and implement their
+ // coalescence with normal hide and show events. Note, in this case they
+ // should be coalesced with normal show/hide events.
+
+ nsIContent* parent = aContent->GetFlattenedTreeParent();
+ ContentRemoved(parent, aContent);
+ ContentInserted(parent, aContent, aContent->GetNextSibling());
+}
+
+void
+DocAccessible::ProcessInvalidationList()
+{
+ // Invalidate children of container accessible for each element in
+ // invalidation list. Allow invalidation list insertions while container
+ // children are recached.
+ for (uint32_t idx = 0; idx < mInvalidationList.Length(); idx++) {
+ nsIContent* content = mInvalidationList[idx];
+ if (!HasAccessible(content) && content->HasID()) {
+ Accessible* container = GetContainerAccessible(content);
+ if (container) {
+ // Check if the node is a target of aria-owns, and if so, don't process
+ // it here and let DoARIAOwnsRelocation process it.
+ AttrRelProviderArray* list =
+ mDependentIDsHash.Get(nsDependentAtomString(content->GetID()));
+ bool shouldProcess = !!list;
+ if (shouldProcess) {
+ for (uint32_t idx = 0; idx < list->Length(); idx++) {
+ if (list->ElementAt(idx)->mRelAttr == nsGkAtoms::aria_owns) {
+ shouldProcess = false;
+ break;
+ }
+ }
+
+ if (shouldProcess) {
+ ProcessContentInserted(container, content);
+ }
+ }
+ }
+ }
+ }
+
+ mInvalidationList.Clear();
+}
+
+Accessible*
+DocAccessible::GetAccessibleEvenIfNotInMap(nsINode* aNode) const
+{
+if (!aNode->IsContent() || !aNode->AsContent()->IsHTMLElement(nsGkAtoms::area))
+ return GetAccessible(aNode);
+
+ // XXX Bug 135040, incorrect when multiple images use the same map.
+ nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
+ nsImageFrame* imageFrame = do_QueryFrame(frame);
+ if (imageFrame) {
+ Accessible* parent = GetAccessible(imageFrame->GetContent());
+ if (parent) {
+ Accessible* area =
+ parent->AsImageMap()->GetChildAccessibleFor(aNode);
+ if (area)
+ return area;
+
+ return nullptr;
+ }
+ }
+
+ return GetAccessible(aNode);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Protected members
+
+void
+DocAccessible::NotifyOfLoading(bool aIsReloading)
+{
+ // Mark the document accessible as loading, if it stays alive then we'll mark
+ // it as loaded when we receive proper notification.
+ mLoadState &= ~eDOMLoaded;
+
+ if (!IsLoadEventTarget())
+ return;
+
+ if (aIsReloading) {
+ // Fire reload and state busy events on existing document accessible while
+ // event from user input flag can be calculated properly and accessible
+ // is alive. When new document gets loaded then this one is destroyed.
+ RefPtr<AccEvent> reloadEvent =
+ new AccEvent(nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD, this);
+ nsEventShell::FireEvent(reloadEvent);
+ }
+
+ // Fire state busy change event. Use delayed event since we don't care
+ // actually if event isn't delivered when the document goes away like a shot.
+ RefPtr<AccEvent> stateEvent =
+ new AccStateChangeEvent(this, states::BUSY, true);
+ FireDelayedEvent(stateEvent);
+}
+
+void
+DocAccessible::DoInitialUpdate()
+{
+ if (nsCoreUtils::IsTabDocument(mDocumentNode))
+ mDocFlags |= eTabDocument;
+
+ mLoadState |= eTreeConstructed;
+
+ // Set up a root element and ARIA role mapping.
+ UpdateRootElIfNeeded();
+
+ // Build initial tree.
+ CacheChildrenInSubtree(this);
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eVerbose)) {
+ logging::Tree("TREE", "Initial subtree", this);
+ }
+#endif
+
+ // Fire reorder event after the document tree is constructed. Note, since
+ // this reorder event is processed by parent document then events targeted to
+ // this document may be fired prior to this reorder event. If this is
+ // a problem then consider to keep event processing per tab document.
+ if (!IsRoot()) {
+ RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(Parent());
+ ParentDocument()->FireDelayedEvent(reorderEvent);
+ }
+
+ TreeMutation mt(this);
+ uint32_t childCount = ChildCount();
+ for (uint32_t i = 0; i < childCount; i++) {
+ Accessible* child = GetChildAt(i);
+ mt.AfterInsertion(child);
+ }
+ mt.Done();
+}
+
+void
+DocAccessible::ProcessLoad()
+{
+ mLoadState |= eCompletelyLoaded;
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocLoad))
+ logging::DocCompleteLoad(this, IsLoadEventTarget());
+#endif
+
+ // Do not fire document complete/stop events for root chrome document
+ // accessibles and for frame/iframe documents because
+ // a) screen readers start working on focus event in the case of root chrome
+ // documents
+ // b) document load event on sub documents causes screen readers to act is if
+ // entire page is reloaded.
+ if (!IsLoadEventTarget())
+ return;
+
+ // Fire complete/load stopped if the load event type is given.
+ if (mLoadEventType) {
+ RefPtr<AccEvent> loadEvent = new AccEvent(mLoadEventType, this);
+ FireDelayedEvent(loadEvent);
+
+ mLoadEventType = 0;
+ }
+
+ // Fire busy state change event.
+ RefPtr<AccEvent> stateEvent =
+ new AccStateChangeEvent(this, states::BUSY, false);
+ FireDelayedEvent(stateEvent);
+}
+
+void
+DocAccessible::AddDependentIDsFor(Accessible* aRelProvider, nsIAtom* aRelAttr)
+{
+ dom::Element* relProviderEl = aRelProvider->Elm();
+ if (!relProviderEl)
+ return;
+
+ for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) {
+ nsIAtom* relAttr = *kRelationAttrs[idx];
+ if (aRelAttr && aRelAttr != relAttr)
+ continue;
+
+ if (relAttr == nsGkAtoms::_for) {
+ if (!relProviderEl->IsAnyOfHTMLElements(nsGkAtoms::label,
+ nsGkAtoms::output))
+ continue;
+
+ } else if (relAttr == nsGkAtoms::control) {
+ if (!relProviderEl->IsAnyOfXULElements(nsGkAtoms::label,
+ nsGkAtoms::description))
+ continue;
+ }
+
+ IDRefsIterator iter(this, relProviderEl, relAttr);
+ while (true) {
+ const nsDependentSubstring id = iter.NextID();
+ if (id.IsEmpty())
+ break;
+
+ AttrRelProviderArray* providers = mDependentIDsHash.Get(id);
+ if (!providers) {
+ providers = new AttrRelProviderArray();
+ if (providers) {
+ mDependentIDsHash.Put(id, providers);
+ }
+ }
+
+ if (providers) {
+ AttrRelProvider* provider =
+ new AttrRelProvider(relAttr, relProviderEl);
+ if (provider) {
+ providers->AppendElement(provider);
+
+ // We've got here during the children caching. If the referenced
+ // content is not accessible then store it to pend its container
+ // children invalidation (this happens immediately after the caching
+ // is finished).
+ nsIContent* dependentContent = iter.GetElem(id);
+ if (dependentContent) {
+ if (!HasAccessible(dependentContent)) {
+ mInvalidationList.AppendElement(dependentContent);
+ }
+ }
+ }
+ }
+ }
+
+ // If the relation attribute is given then we don't have anything else to
+ // check.
+ if (aRelAttr)
+ break;
+ }
+
+ // Make sure to schedule the tree update if needed.
+ mNotificationController->ScheduleProcessing();
+}
+
+void
+DocAccessible::RemoveDependentIDsFor(Accessible* aRelProvider,
+ nsIAtom* aRelAttr)
+{
+ dom::Element* relProviderElm = aRelProvider->Elm();
+ if (!relProviderElm)
+ return;
+
+ for (uint32_t idx = 0; idx < kRelationAttrsLen; idx++) {
+ nsIAtom* relAttr = *kRelationAttrs[idx];
+ if (aRelAttr && aRelAttr != *kRelationAttrs[idx])
+ continue;
+
+ IDRefsIterator iter(this, relProviderElm, relAttr);
+ while (true) {
+ const nsDependentSubstring id = iter.NextID();
+ if (id.IsEmpty())
+ break;
+
+ AttrRelProviderArray* providers = mDependentIDsHash.Get(id);
+ if (providers) {
+ for (uint32_t jdx = 0; jdx < providers->Length(); ) {
+ AttrRelProvider* provider = (*providers)[jdx];
+ if (provider->mRelAttr == relAttr &&
+ provider->mContent == relProviderElm)
+ providers->RemoveElement(provider);
+ else
+ jdx++;
+ }
+ if (providers->Length() == 0)
+ mDependentIDsHash.Remove(id);
+ }
+ }
+
+ // If the relation attribute is given then we don't have anything else to
+ // check.
+ if (aRelAttr)
+ break;
+ }
+}
+
+bool
+DocAccessible::UpdateAccessibleOnAttrChange(dom::Element* aElement,
+ nsIAtom* aAttribute)
+{
+ if (aAttribute == nsGkAtoms::role) {
+ // It is common for js libraries to set the role on the body element after
+ // the document has loaded. In this case we just update the role map entry.
+ if (mContent == aElement) {
+ SetRoleMapEntry(aria::GetRoleMap(aElement));
+ if (mIPCDoc) {
+ mIPCDoc->SendRoleChangedEvent(Role());
+ }
+
+ return true;
+ }
+
+ // Recreate the accessible when role is changed because we might require a
+ // different accessible class for the new role or the accessible may expose
+ // a different sets of interfaces (COM restriction).
+ RecreateAccessible(aElement);
+
+ return true;
+ }
+
+ if (aAttribute == nsGkAtoms::href) {
+ // Not worth the expense to ensure which namespace these are in. It doesn't
+ // kill use to recreate the accessible even if the attribute was used in
+ // the wrong namespace or an element that doesn't support it.
+
+ // Make sure the accessible is recreated asynchronously to allow the content
+ // to handle the attribute change.
+ RecreateAccessible(aElement);
+ return true;
+ }
+
+ if (aAttribute == nsGkAtoms::aria_multiselectable &&
+ aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::role)) {
+ // This affects whether the accessible supports SelectAccessible.
+ // COM says we cannot change what interfaces are supported on-the-fly,
+ // so invalidate this object. A new one will be created on demand.
+ RecreateAccessible(aElement);
+
+ return true;
+ }
+
+ return false;
+}
+
+void
+DocAccessible::UpdateRootElIfNeeded()
+{
+ dom::Element* rootEl = mDocumentNode->GetBodyElement();
+ if (!rootEl) {
+ rootEl = mDocumentNode->GetRootElement();
+ }
+ if (rootEl != mContent) {
+ mContent = rootEl;
+ SetRoleMapEntry(aria::GetRoleMap(rootEl));
+ if (mIPCDoc) {
+ mIPCDoc->SendRoleChangedEvent(Role());
+ }
+ }
+}
+
+/**
+ * Content insertion helper.
+ */
+class InsertIterator final
+{
+public:
+ InsertIterator(Accessible* aContext,
+ const nsTArray<nsCOMPtr<nsIContent> >* aNodes) :
+ mChild(nullptr), mChildBefore(nullptr), mWalker(aContext),
+ mNodes(aNodes), mNodesIdx(0)
+ {
+ MOZ_ASSERT(aContext, "No context");
+ MOZ_ASSERT(aNodes, "No nodes to search for accessible elements");
+ MOZ_COUNT_CTOR(InsertIterator);
+ }
+ ~InsertIterator() { MOZ_COUNT_DTOR(InsertIterator); }
+
+ Accessible* Context() const { return mWalker.Context(); }
+ Accessible* Child() const { return mChild; }
+ Accessible* ChildBefore() const { return mChildBefore; }
+ DocAccessible* Document() const { return mWalker.Document(); }
+
+ /**
+ * Iterates to a next accessible within the inserted content.
+ */
+ bool Next();
+
+ void Rejected()
+ {
+ mChild = nullptr;
+ mChildBefore = nullptr;
+ }
+
+private:
+ Accessible* mChild;
+ Accessible* mChildBefore;
+ TreeWalker mWalker;
+
+ const nsTArray<nsCOMPtr<nsIContent> >* mNodes;
+ uint32_t mNodesIdx;
+};
+
+bool
+InsertIterator::Next()
+{
+ if (mNodesIdx > 0) {
+ Accessible* nextChild = mWalker.Next();
+ if (nextChild) {
+ mChildBefore = mChild;
+ mChild = nextChild;
+ return true;
+ }
+ }
+
+ while (mNodesIdx < mNodes->Length()) {
+ // Ignore nodes that are not contained by the container anymore.
+
+ // The container might be changed, for example, because of the subsequent
+ // overlapping content insertion (i.e. other content was inserted between
+ // this inserted content and its container or the content was reinserted
+ // into different container of unrelated part of tree). To avoid a double
+ // processing of the content insertion ignore this insertion notification.
+ // Note, the inserted content might be not in tree at all at this point
+ // what means there's no container. Ignore the insertion too.
+ nsIContent* prevNode = mNodes->SafeElementAt(mNodesIdx - 1);
+ nsIContent* node = mNodes->ElementAt(mNodesIdx++);
+ Accessible* container = Document()->AccessibleOrTrueContainer(node);
+ if (container != Context()) {
+ continue;
+ }
+
+ // HTML comboboxes have no-content list accessible as an intermediate
+ // containing all options.
+ if (container->IsHTMLCombobox()) {
+ container = container->FirstChild();
+ }
+
+ if (!container->IsAcceptableChild(node)) {
+ continue;
+ }
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("traversing an inserted node", logging::eVerbose,
+ "container", container, "node", node);
+#endif
+
+ // If inserted nodes are siblings then just move the walker next.
+ if (mChild && prevNode && prevNode->GetNextSibling() == node) {
+ Accessible* nextChild = mWalker.Scope(node);
+ if (nextChild) {
+ mChildBefore = mChild;
+ mChild = nextChild;
+ return true;
+ }
+ }
+ else {
+ TreeWalker finder(container);
+ if (finder.Seek(node)) {
+ mChild = mWalker.Scope(node);
+ if (mChild) {
+ mChildBefore = finder.Prev();
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+void
+DocAccessible::ProcessContentInserted(Accessible* aContainer,
+ const nsTArray<nsCOMPtr<nsIContent> >* aNodes)
+{
+ // Process insertions if the container accessible is still in tree.
+ if (!aContainer->IsInDocument()) {
+ return;
+ }
+
+ // If new root content has been inserted then update it.
+ if (aContainer == this) {
+ UpdateRootElIfNeeded();
+ }
+
+ InsertIterator iter(aContainer, aNodes);
+ if (!iter.Next()) {
+ return;
+ }
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("children before insertion", logging::eVerbose,
+ aContainer);
+#endif
+
+ TreeMutation mt(aContainer);
+ do {
+ Accessible* parent = iter.Child()->Parent();
+ if (parent) {
+ if (parent != aContainer) {
+#ifdef A11Y_LOG
+ logging::TreeInfo("stealing accessible", 0,
+ "old parent", parent, "new parent",
+ aContainer, "child", iter.Child(), nullptr);
+#endif
+ MOZ_ASSERT_UNREACHABLE("stealing accessible");
+ continue;
+ }
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("binding to same parent", logging::eVerbose,
+ "parent", aContainer, "child", iter.Child(), nullptr);
+#endif
+ continue;
+ }
+
+ if (aContainer->InsertAfter(iter.Child(), iter.ChildBefore())) {
+#ifdef A11Y_LOG
+ logging::TreeInfo("accessible was inserted", 0,
+ "container", aContainer, "child", iter.Child(), nullptr);
+#endif
+
+ CreateSubtree(iter.Child());
+ mt.AfterInsertion(iter.Child());
+ continue;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("accessible was rejected");
+ iter.Rejected();
+ } while (iter.Next());
+
+ mt.Done();
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("children after insertion", logging::eVerbose,
+ aContainer);
+#endif
+
+ FireEventsOnInsertion(aContainer);
+}
+
+void
+DocAccessible::ProcessContentInserted(Accessible* aContainer, nsIContent* aNode)
+{
+ if (!aContainer->IsInDocument()) {
+ return;
+ }
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("children before insertion", logging::eVerbose, aContainer);
+#endif
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("traversing an inserted node", logging::eVerbose,
+ "container", aContainer, "node", aNode);
+#endif
+
+ TreeWalker walker(aContainer);
+ if (aContainer->IsAcceptableChild(aNode) && walker.Seek(aNode)) {
+ Accessible* child = GetAccessible(aNode);
+ if (!child) {
+ child = GetAccService()->CreateAccessible(aNode, aContainer);
+ }
+
+ if (child) {
+ TreeMutation mt(aContainer);
+ if (!aContainer->InsertAfter(child, walker.Prev())) {
+ return;
+ }
+ CreateSubtree(child);
+ mt.AfterInsertion(child);
+ mt.Done();
+
+ FireEventsOnInsertion(aContainer);
+ }
+ }
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("children after insertion", logging::eVerbose, aContainer);
+#endif
+}
+
+void
+DocAccessible::FireEventsOnInsertion(Accessible* aContainer)
+{
+ // Check to see if change occurred inside an alert, and fire an EVENT_ALERT
+ // if it did.
+ if (aContainer->IsAlert() || aContainer->IsInsideAlert()) {
+ Accessible* ancestor = aContainer;
+ do {
+ if (ancestor->IsAlert()) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_ALERT, ancestor);
+ break;
+ }
+ }
+ while ((ancestor = ancestor->Parent()));
+ }
+}
+
+void
+DocAccessible::UpdateTreeOnRemoval(Accessible* aContainer, nsIContent* aChildNode)
+{
+ // If child node is not accessible then look for its accessible children.
+ Accessible* child = GetAccessible(aChildNode);
+#ifdef A11Y_LOG
+ logging::TreeInfo("process content removal", 0,
+ "container", aContainer, "child", aChildNode);
+#endif
+
+ TreeMutation mt(aContainer);
+ if (child) {
+ RefPtr<Accessible> kungFuDeathGripChild(child);
+ mt.BeforeRemoval(child);
+ if (child->IsDefunct()) {
+ return; // event coalescence may kill us
+ }
+
+ MOZ_ASSERT(aContainer == child->Parent(), "Wrong parent");
+ aContainer->RemoveChild(child);
+ UncacheChildrenInSubtree(child);
+ mt.Done();
+ return;
+ }
+
+ TreeWalker walker(aContainer, aChildNode, TreeWalker::eWalkCache);
+ while (Accessible* child = walker.Next()) {
+ RefPtr<Accessible> kungFuDeathGripChild(child);
+ mt.BeforeRemoval(child);
+ if (child->IsDefunct()) {
+ return; // event coalescence may kill us
+ }
+
+ MOZ_ASSERT(aContainer == child->Parent(), "Wrong parent");
+ aContainer->RemoveChild(child);
+ UncacheChildrenInSubtree(child);
+ }
+ mt.Done();
+}
+
+bool
+DocAccessible::RelocateARIAOwnedIfNeeded(nsIContent* aElement)
+{
+ if (!aElement->HasID())
+ return false;
+
+ AttrRelProviderArray* list =
+ mDependentIDsHash.Get(nsDependentAtomString(aElement->GetID()));
+ if (list) {
+ for (uint32_t idx = 0; idx < list->Length(); idx++) {
+ if (list->ElementAt(idx)->mRelAttr == nsGkAtoms::aria_owns) {
+ Accessible* owner = GetAccessible(list->ElementAt(idx)->mContent);
+ if (owner) {
+ mNotificationController->ScheduleRelocation(owner);
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+void
+DocAccessible::ValidateARIAOwned()
+{
+ for (auto it = mARIAOwnsHash.Iter(); !it.Done(); it.Next()) {
+ Accessible* owner = it.Key();
+ nsTArray<RefPtr<Accessible> >* children = it.UserData();
+
+ // Owner is about to die, put children back if applicable.
+ if (!mAccessibleCache.GetWeak(reinterpret_cast<void*>(owner)) ||
+ !owner->IsInDocument()) {
+ PutChildrenBack(children, 0);
+ it.Remove();
+ continue;
+ }
+
+ for (uint32_t idx = 0; idx < children->Length(); idx++) {
+ Accessible* child = children->ElementAt(idx);
+ if (!child->IsInDocument()) {
+ children->RemoveElementAt(idx);
+ idx--;
+ continue;
+ }
+
+ NS_ASSERTION(child->Parent(), "No parent for ARIA owned?");
+
+ // If DOM node doesn't have a frame anymore then shutdown its accessible.
+ if (child->Parent() && !child->GetFrame()) {
+ UpdateTreeOnRemoval(child->Parent(), child->GetContent());
+ children->RemoveElementAt(idx);
+ idx--;
+ continue;
+ }
+
+ NS_ASSERTION(child->Parent() == owner,
+ "Illigally stolen ARIA owned child!");
+ }
+
+ if (children->Length() == 0) {
+ it.Remove();
+ }
+ }
+}
+
+void
+DocAccessible::DoARIAOwnsRelocation(Accessible* aOwner)
+{
+ MOZ_ASSERT(aOwner, "aOwner must be a valid pointer");
+ MOZ_ASSERT(aOwner->Elm(), "aOwner->Elm() must be a valid pointer");
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("aria owns relocation", logging::eVerbose, aOwner);
+#endif
+
+ nsTArray<RefPtr<Accessible> >* owned = mARIAOwnsHash.LookupOrAdd(aOwner);
+ IDRefsIterator iter(this, aOwner->Elm(), nsGkAtoms::aria_owns);
+ uint32_t idx = 0;
+ while (nsIContent* childEl = iter.NextElem()) {
+ Accessible* child = GetAccessible(childEl);
+ auto insertIdx = aOwner->ChildCount() - owned->Length() + idx;
+
+ // Make an attempt to create an accessible if it wasn't created yet.
+ if (!child) {
+ if (aOwner->IsAcceptableChild(childEl)) {
+ child = GetAccService()->CreateAccessible(childEl, aOwner);
+ if (child) {
+ TreeMutation imut(aOwner);
+ aOwner->InsertChildAt(insertIdx, child);
+ imut.AfterInsertion(child);
+ imut.Done();
+
+ child->SetRelocated(true);
+ owned->InsertElementAt(idx, child);
+ idx++;
+
+ // Create subtree before adjusting the insertion index, since subtree
+ // creation may alter children in the container.
+ CreateSubtree(child);
+ FireEventsOnInsertion(aOwner);
+ }
+ }
+ continue;
+ }
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("aria owns traversal", logging::eVerbose,
+ "candidate", child, nullptr);
+#endif
+
+ // Same child on same position, no change.
+ if (child->Parent() == aOwner &&
+ child->IndexInParent() == static_cast<int32_t>(insertIdx)) {
+ MOZ_ASSERT(owned->ElementAt(idx) == child, "Not in sync!");
+ idx++;
+ continue;
+ }
+
+ MOZ_ASSERT(owned->SafeElementAt(idx) != child, "Already in place!");
+ if (owned->IndexOf(child) < idx) {
+ continue; // ignore second entry of same ID
+ }
+
+ // A new child is found, check for loops.
+ if (child->Parent() != aOwner) {
+ Accessible* parent = aOwner;
+ while (parent && parent != child && !parent->IsDoc()) {
+ parent = parent->Parent();
+ }
+ // A referred child cannot be a parent of the owner.
+ if (parent == child) {
+ continue;
+ }
+ }
+
+ if (MoveChild(child, aOwner, insertIdx)) {
+ child->SetRelocated(true);
+ MOZ_ASSERT(owned == mARIAOwnsHash.Get(aOwner));
+ owned = mARIAOwnsHash.LookupOrAdd(aOwner);
+ owned->InsertElementAt(idx, child);
+ idx++;
+ }
+ }
+
+ // Put back children that are not seized anymore.
+ PutChildrenBack(owned, idx);
+ if (owned->Length() == 0) {
+ mARIAOwnsHash.Remove(aOwner);
+ }
+}
+
+void
+DocAccessible::PutChildrenBack(nsTArray<RefPtr<Accessible> >* aChildren,
+ uint32_t aStartIdx)
+{
+ MOZ_ASSERT(aStartIdx <= aChildren->Length(), "Wrong removal index");
+
+ nsTArray<RefPtr<Accessible> > containers;
+ for (auto idx = aStartIdx; idx < aChildren->Length(); idx++) {
+ Accessible* child = aChildren->ElementAt(idx);
+ if (!child->IsInDocument()) {
+ continue;
+ }
+
+ // Remove the child from the owner
+ Accessible* owner = child->Parent();
+ if (!owner) {
+ NS_ERROR("Cannot put the child back. No parent, a broken tree.");
+ continue;
+ }
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("aria owns put child back", 0,
+ "old parent", owner, "child", child, nullptr);
+#endif
+
+ // Unset relocated flag to find an insertion point for the child.
+ child->SetRelocated(false);
+
+ int32_t idxInParent = -1;
+ Accessible* origContainer = GetContainerAccessible(child->GetContent());
+ if (origContainer) {
+ TreeWalker walker(origContainer);
+ if (walker.Seek(child->GetContent())) {
+ Accessible* prevChild = walker.Prev();
+ if (prevChild) {
+ idxInParent = prevChild->IndexInParent() + 1;
+ MOZ_ASSERT(origContainer == prevChild->Parent(), "Broken tree");
+ origContainer = prevChild->Parent();
+ }
+ else {
+ idxInParent = 0;
+ }
+ }
+ }
+ MoveChild(child, origContainer, idxInParent);
+ }
+
+ aChildren->RemoveElementsAt(aStartIdx, aChildren->Length() - aStartIdx);
+}
+
+bool
+DocAccessible::MoveChild(Accessible* aChild, Accessible* aNewParent,
+ int32_t aIdxInParent)
+{
+ MOZ_ASSERT(aChild, "No child");
+ MOZ_ASSERT(aChild->Parent(), "No parent");
+ MOZ_ASSERT(aIdxInParent <= static_cast<int32_t>(aNewParent->ChildCount()),
+ "Wrong insertion point for a moving child");
+
+ Accessible* curParent = aChild->Parent();
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("move child", 0,
+ "old parent", curParent, "new parent", aNewParent,
+ "child", aChild, nullptr);
+#endif
+
+ // Forget aria-owns info in case of ARIA owned element. The caller is expected
+ // to update it if needed.
+ if (aChild->IsRelocated()) {
+ aChild->SetRelocated(false);
+ nsTArray<RefPtr<Accessible> >* children = mARIAOwnsHash.Get(curParent);
+ children->RemoveElement(aChild);
+ }
+
+ NotificationController::MoveGuard mguard(mNotificationController);
+
+ if (curParent == aNewParent) {
+ MOZ_ASSERT(aChild->IndexInParent() != aIdxInParent, "No move case");
+ curParent->MoveChild(aIdxInParent, aChild);
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("move child: parent tree after",
+ logging::eVerbose, curParent);
+#endif
+ return true;
+ }
+
+ if (!aNewParent->IsAcceptableChild(aChild->GetContent())) {
+ return false;
+ }
+
+ TreeMutation rmut(curParent);
+ rmut.BeforeRemoval(aChild, TreeMutation::kNoShutdown);
+ curParent->RemoveChild(aChild);
+ rmut.Done();
+
+ // No insertion point for the child.
+ if (aIdxInParent == -1) {
+ return true;
+ }
+
+ if (aIdxInParent > static_cast<int32_t>(aNewParent->ChildCount())) {
+ MOZ_ASSERT_UNREACHABLE("Wrong insertion point for a moving child");
+ return true;
+ }
+
+ TreeMutation imut(aNewParent);
+ aNewParent->InsertChildAt(aIdxInParent, aChild);
+ imut.AfterInsertion(aChild);
+ imut.Done();
+
+#ifdef A11Y_LOG
+ logging::TreeInfo("move child: old parent tree after",
+ logging::eVerbose, curParent);
+ logging::TreeInfo("move child: new parent tree after",
+ logging::eVerbose, aNewParent);
+#endif
+
+ return true;
+}
+
+
+void
+DocAccessible::CacheChildrenInSubtree(Accessible* aRoot,
+ Accessible** aFocusedAcc)
+{
+ // If the accessible is focused then report a focus event after all related
+ // mutation events.
+ if (aFocusedAcc && !*aFocusedAcc &&
+ FocusMgr()->HasDOMFocus(aRoot->GetContent()))
+ *aFocusedAcc = aRoot;
+
+ Accessible* root = aRoot->IsHTMLCombobox() ? aRoot->FirstChild() : aRoot;
+ if (root->KidsFromDOM()) {
+ TreeMutation mt(root, TreeMutation::kNoEvents);
+ TreeWalker walker(root);
+ while (Accessible* child = walker.Next()) {
+ if (child->IsBoundToParent()) {
+ MoveChild(child, root, root->ChildCount());
+ continue;
+ }
+
+ root->AppendChild(child);
+ mt.AfterInsertion(child);
+
+ CacheChildrenInSubtree(child, aFocusedAcc);
+ }
+ mt.Done();
+ }
+
+ // Fire events for ARIA elements.
+ if (!aRoot->HasARIARole()) {
+ return;
+ }
+
+ // XXX: we should delay document load complete event if the ARIA document
+ // has aria-busy.
+ roles::Role role = aRoot->ARIARole();
+ if (!aRoot->IsDoc() && (role == roles::DIALOG || role == roles::DOCUMENT)) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE, aRoot);
+ }
+}
+
+void
+DocAccessible::UncacheChildrenInSubtree(Accessible* aRoot)
+{
+ aRoot->mStateFlags |= eIsNotInDocument;
+ RemoveDependentIDsFor(aRoot);
+
+ uint32_t count = aRoot->ContentChildCount();
+ for (uint32_t idx = 0; idx < count; idx++) {
+ Accessible* child = aRoot->ContentChildAt(idx);
+
+ // Removing this accessible from the document doesn't mean anything about
+ // accessibles for subdocuments, so skip removing those from the tree.
+ if (!child->IsDoc()) {
+ UncacheChildrenInSubtree(child);
+ }
+ }
+
+ if (aRoot->IsNodeMapEntry() &&
+ mNodeToAccessibleMap.Get(aRoot->GetNode()) == aRoot)
+ mNodeToAccessibleMap.Remove(aRoot->GetNode());
+}
+
+void
+DocAccessible::ShutdownChildrenInSubtree(Accessible* aAccessible)
+{
+ // Traverse through children and shutdown them before this accessible. When
+ // child gets shutdown then it removes itself from children array of its
+ //parent. Use jdx index to process the cases if child is not attached to the
+ // parent and as result doesn't remove itself from its children.
+ uint32_t count = aAccessible->ContentChildCount();
+ for (uint32_t idx = 0, jdx = 0; idx < count; idx++) {
+ Accessible* child = aAccessible->ContentChildAt(jdx);
+ if (!child->IsBoundToParent()) {
+ NS_ERROR("Parent refers to a child, child doesn't refer to parent!");
+ jdx++;
+ }
+
+ // Don't cross document boundaries. The outerdoc shutdown takes care about
+ // its subdocument.
+ if (!child->IsDoc())
+ ShutdownChildrenInSubtree(child);
+ }
+
+ UnbindFromDocument(aAccessible);
+}
+
+bool
+DocAccessible::IsLoadEventTarget() const
+{
+ nsCOMPtr<nsIDocShellTreeItem> treeItem = mDocumentNode->GetDocShell();
+ NS_ASSERTION(treeItem, "No document shell for document!");
+
+ nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
+ treeItem->GetParent(getter_AddRefs(parentTreeItem));
+
+ // Not a root document.
+ if (parentTreeItem) {
+ // Return true if it's either:
+ // a) tab document;
+ nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
+ treeItem->GetRootTreeItem(getter_AddRefs(rootTreeItem));
+ if (parentTreeItem == rootTreeItem)
+ return true;
+
+ // b) frame/iframe document and its parent document is not in loading state
+ // Note: we can get notifications while document is loading (and thus
+ // while there's no parent document yet).
+ DocAccessible* parentDoc = ParentDocument();
+ return parentDoc && parentDoc->HasLoadState(eCompletelyLoaded);
+ }
+
+ // It's content (not chrome) root document.
+ return (treeItem->ItemType() == nsIDocShellTreeItem::typeContent);
+}
diff --git a/accessible/generic/DocAccessible.h b/accessible/generic/DocAccessible.h
new file mode 100644
index 000000000..4bc6f03f0
--- /dev/null
+++ b/accessible/generic/DocAccessible.h
@@ -0,0 +1,718 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_DocAccessible_h__
+#define mozilla_a11y_DocAccessible_h__
+
+#include "nsIAccessiblePivot.h"
+
+#include "HyperTextAccessibleWrap.h"
+#include "AccEvent.h"
+
+#include "nsAutoPtr.h"
+#include "nsClassHashtable.h"
+#include "nsDataHashtable.h"
+#include "nsIDocument.h"
+#include "nsIDocumentObserver.h"
+#include "nsIEditor.h"
+#include "nsIObserver.h"
+#include "nsIScrollPositionListener.h"
+#include "nsITimer.h"
+#include "nsIWeakReference.h"
+
+class nsAccessiblePivot;
+
+const uint32_t kDefaultCacheLength = 128;
+
+namespace mozilla {
+namespace a11y {
+
+class DocManager;
+class NotificationController;
+class DocAccessibleChild;
+class RelatedAccIterator;
+template<class Class, class ... Args>
+class TNotification;
+
+class DocAccessible : public HyperTextAccessibleWrap,
+ public nsIDocumentObserver,
+ public nsIObserver,
+ public nsIScrollPositionListener,
+ public nsSupportsWeakReference,
+ public nsIAccessiblePivotObserver
+{
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DocAccessible, Accessible)
+
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIACCESSIBLEPIVOTOBSERVER
+
+public:
+
+ DocAccessible(nsIDocument* aDocument, nsIPresShell* aPresShell);
+
+ // nsIScrollPositionListener
+ virtual void ScrollPositionWillChange(nscoord aX, nscoord aY) override {}
+ virtual void ScrollPositionDidChange(nscoord aX, nscoord aY) override;
+
+ // nsIDocumentObserver
+ NS_DECL_NSIDOCUMENTOBSERVER
+
+ // Accessible
+ virtual void Init();
+ virtual void Shutdown() override;
+ virtual nsIFrame* GetFrame() const override;
+ virtual nsINode* GetNode() const override { return mDocumentNode; }
+ nsIDocument* DocumentNode() const { return mDocumentNode; }
+
+ virtual mozilla::a11y::ENameValueFlag Name(nsString& aName) override;
+ virtual void Description(nsString& aDescription) override;
+ virtual Accessible* FocusedChild() override;
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+ virtual uint64_t NativeInteractiveState() const override;
+ virtual bool NativelyUnavailable() const override;
+ virtual void ApplyARIAState(uint64_t* aState) const override;
+ virtual already_AddRefed<nsIPersistentProperties> Attributes() override;
+
+ virtual void TakeFocus() override;
+
+#ifdef A11Y_LOG
+ virtual nsresult HandleAccEvent(AccEvent* aEvent) override;
+#endif
+
+ virtual nsRect RelativeBounds(nsIFrame** aRelativeFrame) const override;
+
+ // HyperTextAccessible
+ virtual already_AddRefed<nsIEditor> GetEditor() const override;
+
+ // DocAccessible
+
+ /**
+ * Return document URL.
+ */
+ void URL(nsAString& aURL) const;
+
+ /**
+ * Return DOM document title.
+ */
+ void Title(nsString& aTitle) const { mDocumentNode->GetTitle(aTitle); }
+
+ /**
+ * Return DOM document mime type.
+ */
+ void MimeType(nsAString& aType) const { mDocumentNode->GetContentType(aType); }
+
+ /**
+ * Return DOM document type.
+ */
+ void DocType(nsAString& aType) const;
+
+ /**
+ * Return virtual cursor associated with the document.
+ */
+ nsIAccessiblePivot* VirtualCursor();
+
+ /**
+ * Return presentation shell for this document accessible.
+ */
+ nsIPresShell* PresShell() const { return mPresShell; }
+
+ /**
+ * Return the presentation shell's context.
+ */
+ nsPresContext* PresContext() const { return mPresShell->GetPresContext(); }
+
+ /**
+ * Return true if associated DOM document was loaded and isn't unloading.
+ */
+ bool IsContentLoaded() const
+ {
+ // eDOMLoaded flag check is used for error pages as workaround to make this
+ // method return correct result since error pages do not receive 'pageshow'
+ // event and as consequence nsIDocument::IsShowing() returns false.
+ return mDocumentNode && mDocumentNode->IsVisible() &&
+ (mDocumentNode->IsShowing() || HasLoadState(eDOMLoaded));
+ }
+
+ /**
+ * Document load states.
+ */
+ enum LoadState {
+ // initial tree construction is pending
+ eTreeConstructionPending = 0,
+ // initial tree construction done
+ eTreeConstructed = 1,
+ // DOM document is loaded.
+ eDOMLoaded = 1 << 1,
+ // document is ready
+ eReady = eTreeConstructed | eDOMLoaded,
+ // document and all its subdocuments are ready
+ eCompletelyLoaded = eReady | 1 << 2
+ };
+
+ /**
+ * Return true if the document has given document state.
+ */
+ bool HasLoadState(LoadState aState) const
+ { return (mLoadState & static_cast<uint32_t>(aState)) ==
+ static_cast<uint32_t>(aState); }
+
+ /**
+ * Return a native window handler or pointer depending on platform.
+ */
+ virtual void* GetNativeWindow() const;
+
+ /**
+ * Return the parent document.
+ */
+ DocAccessible* ParentDocument() const
+ { return mParent ? mParent->Document() : nullptr; }
+
+ /**
+ * Return the child document count.
+ */
+ uint32_t ChildDocumentCount() const
+ { return mChildDocuments.Length(); }
+
+ /**
+ * Return the child document at the given index.
+ */
+ DocAccessible* GetChildDocumentAt(uint32_t aIndex) const
+ { return mChildDocuments.SafeElementAt(aIndex, nullptr); }
+
+ /**
+ * Fire accessible event asynchronously.
+ */
+ void FireDelayedEvent(AccEvent* aEvent);
+ void FireDelayedEvent(uint32_t aEventType, Accessible* aTarget);
+ void FireEventsOnInsertion(Accessible* aContainer);
+
+ /**
+ * Fire value change event on the given accessible if applicable.
+ */
+ void MaybeNotifyOfValueChange(Accessible* aAccessible);
+
+ /**
+ * Get/set the anchor jump.
+ */
+ Accessible* AnchorJump()
+ { return GetAccessibleOrContainer(mAnchorJumpElm); }
+
+ void SetAnchorJump(nsIContent* aTargetNode)
+ { mAnchorJumpElm = aTargetNode; }
+
+ /**
+ * Bind the child document to the tree.
+ */
+ void BindChildDocument(DocAccessible* aDocument);
+
+ /**
+ * Process the generic notification.
+ *
+ * @note The caller must guarantee that the given instance still exists when
+ * notification is processed.
+ * @see NotificationController::HandleNotification
+ */
+ template<class Class, class Arg>
+ void HandleNotification(Class* aInstance,
+ typename TNotification<Class, Arg>::Callback aMethod,
+ Arg* aArg);
+
+ /**
+ * Return the cached accessible by the given DOM node if it's in subtree of
+ * this document accessible or the document accessible itself, otherwise null.
+ *
+ * @return the accessible object
+ */
+ Accessible* GetAccessible(nsINode* aNode) const
+ {
+ return aNode == mDocumentNode ?
+ const_cast<DocAccessible*>(this) : mNodeToAccessibleMap.Get(aNode);
+ }
+
+ /**
+ * Return an accessible for the given node even if the node is not in
+ * document's node map cache (like HTML area element).
+ *
+ * XXX: it should be really merged with GetAccessible().
+ */
+ Accessible* GetAccessibleEvenIfNotInMap(nsINode* aNode) const;
+ Accessible* GetAccessibleEvenIfNotInMapOrContainer(nsINode* aNode) const;
+
+ /**
+ * Return whether the given DOM node has an accessible or not.
+ */
+ bool HasAccessible(nsINode* aNode) const
+ { return GetAccessible(aNode); }
+
+ /**
+ * Return the cached accessible by the given unique ID within this document.
+ *
+ * @note the unique ID matches with the uniqueID() of Accessible
+ *
+ * @param aUniqueID [in] the unique ID used to cache the node.
+ */
+ Accessible* GetAccessibleByUniqueID(void* aUniqueID)
+ {
+ return UniqueID() == aUniqueID ?
+ this : mAccessibleCache.GetWeak(aUniqueID);
+ }
+
+ /**
+ * Return the cached accessible by the given unique ID looking through
+ * this and nested documents.
+ */
+ Accessible* GetAccessibleByUniqueIDInSubtree(void* aUniqueID);
+
+ /**
+ * Return an accessible for the given DOM node or container accessible if
+ * the node is not accessible.
+ */
+ Accessible* GetAccessibleOrContainer(nsINode* aNode) const;
+
+ /**
+ * Return a container accessible for the given DOM node.
+ */
+ Accessible* GetContainerAccessible(nsINode* aNode) const
+ {
+ return aNode ? GetAccessibleOrContainer(aNode->GetParentNode()) : nullptr;
+ }
+
+ /**
+ * Return an accessible for the given node if any, or an immediate accessible
+ * container for it.
+ */
+ Accessible* AccessibleOrTrueContainer(nsINode* aNode) const;
+
+ /**
+ * Return an accessible for the given node or its first accessible descendant.
+ */
+ Accessible* GetAccessibleOrDescendant(nsINode* aNode) const;
+
+ /**
+ * Returns aria-owns seized child at the given index.
+ */
+ Accessible* ARIAOwnedAt(Accessible* aParent, uint32_t aIndex) const
+ {
+ nsTArray<RefPtr<Accessible> >* children = mARIAOwnsHash.Get(aParent);
+ if (children) {
+ return children->SafeElementAt(aIndex);
+ }
+ return nullptr;
+ }
+ uint32_t ARIAOwnedCount(Accessible* aParent) const
+ {
+ nsTArray<RefPtr<Accessible> >* children = mARIAOwnsHash.Get(aParent);
+ return children ? children->Length() : 0;
+ }
+
+ /**
+ * Return true if the given ID is referred by relation attribute.
+ *
+ * @note Different elements may share the same ID if they are hosted inside
+ * XBL bindings. Be careful the result of this method may be senseless
+ * while it's called for XUL elements (where XBL is used widely).
+ */
+ bool IsDependentID(const nsAString& aID) const
+ { return mDependentIDsHash.Get(aID, nullptr); }
+
+ /**
+ * Initialize the newly created accessible and put it into document caches.
+ *
+ * @param aAccessible [in] created accessible
+ * @param aRoleMapEntry [in] the role map entry role the ARIA role or nullptr
+ * if none
+ */
+ void BindToDocument(Accessible* aAccessible,
+ const nsRoleMapEntry* aRoleMapEntry);
+
+ /**
+ * Remove from document and shutdown the given accessible.
+ */
+ void UnbindFromDocument(Accessible* aAccessible);
+
+ /**
+ * Notify the document accessible that content was inserted.
+ */
+ void ContentInserted(nsIContent* aContainerNode,
+ nsIContent* aStartChildNode,
+ nsIContent* aEndChildNode);
+
+ /**
+ * Notify the document accessible that content was removed.
+ */
+ void ContentRemoved(Accessible* aContainer, nsIContent* aChildNode)
+ {
+ // Update the whole tree of this document accessible when the container is
+ // null (document element is removed).
+ UpdateTreeOnRemoval((aContainer ? aContainer : this), aChildNode);
+ }
+ void ContentRemoved(nsIContent* aContainerNode, nsIContent* aChildNode)
+ {
+ ContentRemoved(GetAccessibleOrContainer(aContainerNode), aChildNode);
+ }
+
+ /**
+ * Updates accessible tree when rendered text is changed.
+ */
+ void UpdateText(nsIContent* aTextNode);
+
+ /**
+ * Recreate an accessible, results in hide/show events pair.
+ */
+ void RecreateAccessible(nsIContent* aContent);
+
+ /**
+ * Schedule ARIA owned element relocation if needed. Return true if relocation
+ * was scheduled.
+ */
+ bool RelocateARIAOwnedIfNeeded(nsIContent* aEl);
+
+ /**
+ * Return a notification controller associated with the document.
+ */
+ NotificationController* Controller() const { return mNotificationController; }
+
+ /**
+ * If this document is in a content process return the object responsible for
+ * communicating with the main process for it.
+ */
+ DocAccessibleChild* IPCDoc() const { return mIPCDoc; }
+
+protected:
+ virtual ~DocAccessible();
+
+ void LastRelease();
+
+ // DocAccessible
+ virtual nsresult AddEventListeners();
+ virtual nsresult RemoveEventListeners();
+
+ /**
+ * Marks this document as loaded or loading.
+ */
+ void NotifyOfLoad(uint32_t aLoadEventType);
+ void NotifyOfLoading(bool aIsReloading);
+
+ friend class DocManager;
+
+ /**
+ * Perform initial update (create accessible tree).
+ * Can be overridden by wrappers to prepare initialization work.
+ */
+ virtual void DoInitialUpdate();
+
+ /**
+ * Updates root element and picks up ARIA role on it if any.
+ */
+ void UpdateRootElIfNeeded();
+
+ /**
+ * Process document load notification, fire document load and state busy
+ * events if applicable.
+ */
+ void ProcessLoad();
+
+ /**
+ * Add/remove scroll listeners, @see nsIScrollPositionListener interface.
+ */
+ void AddScrollListener();
+ void RemoveScrollListener();
+
+ /**
+ * Append the given document accessible to this document's child document
+ * accessibles.
+ */
+ bool AppendChildDocument(DocAccessible* aChildDocument)
+ {
+ return mChildDocuments.AppendElement(aChildDocument);
+ }
+
+ /**
+ * Remove the given document accessible from this document's child document
+ * accessibles.
+ */
+ void RemoveChildDocument(DocAccessible* aChildDocument)
+ {
+ mChildDocuments.RemoveElement(aChildDocument);
+ }
+
+ /**
+ * Add dependent IDs pointed by accessible element by relation attribute to
+ * cache. If the relation attribute is missed then all relation attributes
+ * are checked.
+ *
+ * @param aRelProvider [in] accessible that element has relation attribute
+ * @param aRelAttr [in, optional] relation attribute
+ */
+ void AddDependentIDsFor(Accessible* aRelProvider,
+ nsIAtom* aRelAttr = nullptr);
+
+ /**
+ * Remove dependent IDs pointed by accessible element by relation attribute
+ * from cache. If the relation attribute is absent then all relation
+ * attributes are checked.
+ *
+ * @param aRelProvider [in] accessible that element has relation attribute
+ * @param aRelAttr [in, optional] relation attribute
+ */
+ void RemoveDependentIDsFor(Accessible* aRelProvider,
+ nsIAtom* aRelAttr = nullptr);
+
+ /**
+ * Update or recreate an accessible depending on a changed attribute.
+ *
+ * @param aElement [in] the element the attribute was changed on
+ * @param aAttribute [in] the changed attribute
+ * @return true if an action was taken on the attribute change
+ */
+ bool UpdateAccessibleOnAttrChange(mozilla::dom::Element* aElement,
+ nsIAtom* aAttribute);
+
+ /**
+ * Fire accessible events when attribute is changed.
+ *
+ * @param aAccessible [in] accessible the DOM attribute is changed for
+ * @param aNameSpaceID [in] namespace of changed attribute
+ * @param aAttribute [in] changed attribute
+ */
+ void AttributeChangedImpl(Accessible* aAccessible,
+ int32_t aNameSpaceID, nsIAtom* aAttribute);
+
+ /**
+ * Fire accessible events when ARIA attribute is changed.
+ *
+ * @param aAccessible [in] accesislbe the DOM attribute is changed for
+ * @param aAttribute [in] changed attribute
+ */
+ void ARIAAttributeChanged(Accessible* aAccessible, nsIAtom* aAttribute);
+
+ /**
+ * Process ARIA active-descendant attribute change.
+ */
+ void ARIAActiveDescendantChanged(Accessible* aAccessible);
+
+ /**
+ * Update the accessible tree for inserted content.
+ */
+ void ProcessContentInserted(Accessible* aContainer,
+ const nsTArray<nsCOMPtr<nsIContent> >* aInsertedContent);
+ void ProcessContentInserted(Accessible* aContainer,
+ nsIContent* aInsertedContent);
+
+ /**
+ * Used to notify the document to make it process the invalidation list.
+ *
+ * While children are cached we may encounter the case there's no accessible
+ * for referred content by related accessible. Store these related nodes to
+ * invalidate their containers later.
+ */
+ void ProcessInvalidationList();
+
+ /**
+ * Update the accessible tree for content removal.
+ */
+ void UpdateTreeOnRemoval(Accessible* aContainer, nsIContent* aChildNode);
+
+ /**
+ * Validates all aria-owns connections and updates the tree accordingly.
+ */
+ void ValidateARIAOwned();
+
+ /**
+ * Steals or puts back accessible subtrees.
+ */
+ void DoARIAOwnsRelocation(Accessible* aOwner);
+
+ /**
+ * Moves children back under their original parents.
+ */
+ void PutChildrenBack(nsTArray<RefPtr<Accessible> >* aChildren,
+ uint32_t aStartIdx);
+
+ bool MoveChild(Accessible* aChild, Accessible* aNewParent,
+ int32_t aIdxInParent);
+
+ /**
+ * Create accessible tree.
+ *
+ * @param aRoot [in] a root of subtree to create
+ * @param aFocusedAcc [in, optional] a focused accessible under created
+ * subtree if any
+ */
+ void CacheChildrenInSubtree(Accessible* aRoot,
+ Accessible** aFocusedAcc = nullptr);
+ void CreateSubtree(Accessible* aRoot);
+
+ /**
+ * Remove accessibles in subtree from node to accessible map.
+ */
+ void UncacheChildrenInSubtree(Accessible* aRoot);
+
+ /**
+ * Shutdown any cached accessible in the subtree.
+ *
+ * @param aAccessible [in] the root of the subrtee to invalidate accessible
+ * child/parent refs in
+ */
+ void ShutdownChildrenInSubtree(Accessible* aAccessible);
+
+ /**
+ * Return true if the document is a target of document loading events
+ * (for example, state busy change or document reload events).
+ *
+ * Rules: The root chrome document accessible is never an event target
+ * (for example, Firefox UI window). If the sub document is loaded within its
+ * parent document then the parent document is a target only (aka events
+ * coalescence).
+ */
+ bool IsLoadEventTarget() const;
+
+ /*
+ * Set the object responsible for communicating with the main process on
+ * behalf of this document.
+ */
+ void SetIPCDoc(DocAccessibleChild* aIPCDoc) { mIPCDoc = aIPCDoc; }
+
+ friend class DocAccessibleChildBase;
+
+ /**
+ * Used to fire scrolling end event after page scroll.
+ *
+ * @param aTimer [in] the timer object
+ * @param aClosure [in] the document accessible where scrolling happens
+ */
+ static void ScrollTimerCallback(nsITimer* aTimer, void* aClosure);
+
+protected:
+
+ /**
+ * State and property flags, kept by mDocFlags.
+ */
+ enum {
+ // Whether scroll listeners were added.
+ eScrollInitialized = 1 << 0,
+
+ // Whether the document is a tab document.
+ eTabDocument = 1 << 1
+ };
+
+ /**
+ * Cache of accessibles within this document accessible.
+ */
+ AccessibleHashtable mAccessibleCache;
+ nsDataHashtable<nsPtrHashKey<const nsINode>, Accessible*>
+ mNodeToAccessibleMap;
+
+ nsIDocument* mDocumentNode;
+ nsCOMPtr<nsITimer> mScrollWatchTimer;
+ uint16_t mScrollPositionChangedTicks; // Used for tracking scroll events
+
+ /**
+ * Bit mask of document load states (@see LoadState).
+ */
+ uint32_t mLoadState : 3;
+
+ /**
+ * Bit mask of other states and props.
+ */
+ uint32_t mDocFlags : 28;
+
+ /**
+ * Type of document load event fired after the document is loaded completely.
+ */
+ uint32_t mLoadEventType;
+
+ /**
+ * Reference to anchor jump element.
+ */
+ nsCOMPtr<nsIContent> mAnchorJumpElm;
+
+ /**
+ * A generic state (see items below) before the attribute value was changed.
+ * @see AttributeWillChange and AttributeChanged notifications.
+ */
+ union {
+ // ARIA attribute value
+ nsIAtom* mARIAAttrOldValue;
+
+ // True if the accessible state bit was on
+ bool mStateBitWasOn;
+ };
+
+ nsTArray<RefPtr<DocAccessible> > mChildDocuments;
+
+ /**
+ * The virtual cursor of the document.
+ */
+ RefPtr<nsAccessiblePivot> mVirtualCursor;
+
+ /**
+ * A storage class for pairing content with one of its relation attributes.
+ */
+ class AttrRelProvider
+ {
+ public:
+ AttrRelProvider(nsIAtom* aRelAttr, nsIContent* aContent) :
+ mRelAttr(aRelAttr), mContent(aContent) { }
+
+ nsIAtom* mRelAttr;
+ nsCOMPtr<nsIContent> mContent;
+
+ private:
+ AttrRelProvider();
+ AttrRelProvider(const AttrRelProvider&);
+ AttrRelProvider& operator =(const AttrRelProvider&);
+ };
+
+ /**
+ * The cache of IDs pointed by relation attributes.
+ */
+ typedef nsTArray<nsAutoPtr<AttrRelProvider> > AttrRelProviderArray;
+ nsClassHashtable<nsStringHashKey, AttrRelProviderArray>
+ mDependentIDsHash;
+
+ friend class RelatedAccIterator;
+
+ /**
+ * Used for our caching algorithm. We store the list of nodes that should be
+ * invalidated.
+ *
+ * @see ProcessInvalidationList
+ */
+ nsTArray<RefPtr<nsIContent>> mInvalidationList;
+
+ /**
+ * Holds a list of aria-owns relocations.
+ */
+ nsClassHashtable<nsPtrHashKey<Accessible>, nsTArray<RefPtr<Accessible> > >
+ mARIAOwnsHash;
+
+ /**
+ * Used to process notification from core and accessible events.
+ */
+ RefPtr<NotificationController> mNotificationController;
+ friend class EventTree;
+ friend class NotificationController;
+
+private:
+
+ nsIPresShell* mPresShell;
+
+ // Exclusively owned by IPDL so don't manually delete it!
+ DocAccessibleChild* mIPCDoc;
+};
+
+inline DocAccessible*
+Accessible::AsDoc()
+{
+ return IsDoc() ? static_cast<DocAccessible*>(this) : nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/generic/FormControlAccessible.cpp b/accessible/generic/FormControlAccessible.cpp
new file mode 100644
index 000000000..0f2750070
--- /dev/null
+++ b/accessible/generic/FormControlAccessible.cpp
@@ -0,0 +1,193 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+// NOTE: alphabetically ordered
+
+#include "FormControlAccessible.h"
+#include "Role.h"
+
+#include "mozilla/FloatingPoint.h"
+#include "nsIDOMHTMLFormElement.h"
+#include "nsIDOMXULElement.h"
+#include "nsIDOMXULControlElement.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// ProgressMeterAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+template class mozilla::a11y::ProgressMeterAccessible<1>;
+template class mozilla::a11y::ProgressMeterAccessible<100>;
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible
+
+template<int Max>
+role
+ProgressMeterAccessible<Max>::NativeRole()
+{
+ return roles::PROGRESSBAR;
+}
+
+template<int Max>
+uint64_t
+ProgressMeterAccessible<Max>::NativeState()
+{
+ uint64_t state = LeafAccessible::NativeState();
+
+ // An undetermined progressbar (i.e. without a value) has a mixed state.
+ nsAutoString attrValue;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, attrValue);
+
+ if (attrValue.IsEmpty())
+ state |= states::MIXED;
+
+ return state;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ProgressMeterAccessible<Max>: Widgets
+
+template<int Max>
+bool
+ProgressMeterAccessible<Max>::IsWidget() const
+{
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ProgressMeterAccessible<Max>: Value
+
+template<int Max>
+void
+ProgressMeterAccessible<Max>::Value(nsString& aValue)
+{
+ LeafAccessible::Value(aValue);
+ if (!aValue.IsEmpty())
+ return;
+
+ double maxValue = MaxValue();
+ if (IsNaN(maxValue) || maxValue == 0)
+ return;
+
+ double curValue = CurValue();
+ if (IsNaN(curValue))
+ return;
+
+ // Treat the current value bigger than maximum as 100%.
+ double percentValue = (curValue < maxValue) ?
+ (curValue / maxValue) * 100 : 100;
+
+ aValue.AppendFloat(percentValue);
+ aValue.Append('%');
+}
+
+template<int Max>
+double
+ProgressMeterAccessible<Max>::MaxValue() const
+{
+ double value = LeafAccessible::MaxValue();
+ if (!IsNaN(value))
+ return value;
+
+ nsAutoString strValue;
+ if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::max, strValue)) {
+ nsresult result = NS_OK;
+ value = strValue.ToDouble(&result);
+ if (NS_SUCCEEDED(result))
+ return value;
+ }
+
+ return Max;
+}
+
+template<int Max>
+double
+ProgressMeterAccessible<Max>::MinValue() const
+{
+ double value = LeafAccessible::MinValue();
+ return IsNaN(value) ? 0 : value;
+}
+
+template<int Max>
+double
+ProgressMeterAccessible<Max>::Step() const
+{
+ double value = LeafAccessible::Step();
+ return IsNaN(value) ? 0 : value;
+}
+
+template<int Max>
+double
+ProgressMeterAccessible<Max>::CurValue() const
+{
+ double value = LeafAccessible::CurValue();
+ if (!IsNaN(value))
+ return value;
+
+ nsAutoString attrValue;
+ if (!mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, attrValue))
+ return UnspecifiedNaN<double>();
+
+ nsresult error = NS_OK;
+ value = attrValue.ToDouble(&error);
+ return NS_FAILED(error) ? UnspecifiedNaN<double>() : value;
+}
+
+template<int Max>
+bool
+ProgressMeterAccessible<Max>::SetCurValue(double aValue)
+{
+ return false; // progress meters are readonly.
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RadioButtonAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+RadioButtonAccessible::
+ RadioButtonAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ LeafAccessible(aContent, aDoc)
+{
+}
+
+uint8_t
+RadioButtonAccessible::ActionCount()
+{
+ return 1;
+}
+
+void
+RadioButtonAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ if (aIndex == eAction_Click)
+ aName.AssignLiteral("select");
+}
+
+bool
+RadioButtonAccessible::DoAction(uint8_t aIndex)
+{
+ if (aIndex != eAction_Click)
+ return false;
+
+ DoCommand();
+ return true;
+}
+
+role
+RadioButtonAccessible::NativeRole()
+{
+ return roles::RADIOBUTTON;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RadioButtonAccessible: Widgets
+
+bool
+RadioButtonAccessible::IsWidget() const
+{
+ return true;
+}
diff --git a/accessible/generic/FormControlAccessible.h b/accessible/generic/FormControlAccessible.h
new file mode 100644
index 000000000..59844e553
--- /dev/null
+++ b/accessible/generic/FormControlAccessible.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_FormControlAccessible_H_
+#define MOZILLA_A11Y_FormControlAccessible_H_
+
+#include "BaseAccessibles.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Generic class used for progress meters.
+ */
+template<int Max>
+class ProgressMeterAccessible : public LeafAccessible
+{
+public:
+ ProgressMeterAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ LeafAccessible(aContent, aDoc)
+ {
+ // Ignore 'ValueChange' DOM event in lieu of @value attribute change
+ // notifications.
+ mStateFlags |= eHasNumericValue | eIgnoreDOMUIEvent;
+ mType = eProgressType;
+ }
+
+ // Accessible
+ virtual void Value(nsString& aValue) override;
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+
+ // Value
+ virtual double MaxValue() const override;
+ virtual double MinValue() const override;
+ virtual double CurValue() const override;
+ virtual double Step() const override;
+ virtual bool SetCurValue(double aValue) override;
+
+ // Widgets
+ virtual bool IsWidget() const override;
+
+protected:
+ virtual ~ProgressMeterAccessible() {}
+};
+
+/**
+ * Generic class used for radio buttons.
+ */
+class RadioButtonAccessible : public LeafAccessible
+{
+
+public:
+ RadioButtonAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual mozilla::a11y::role NativeRole() override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+
+ enum { eAction_Click = 0 };
+
+ // Widgets
+ virtual bool IsWidget() const override;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/generic/HyperTextAccessible-inl.h b/accessible/generic/HyperTextAccessible-inl.h
new file mode 100644
index 000000000..1e8deac5d
--- /dev/null
+++ b/accessible/generic/HyperTextAccessible-inl.h
@@ -0,0 +1,180 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_HyperTextAccessible_inl_h__
+#define mozilla_a11y_HyperTextAccessible_inl_h__
+
+#include "HyperTextAccessible.h"
+
+#include "nsAccUtils.h"
+
+#include "nsIClipboard.h"
+#include "nsIEditor.h"
+#include "nsIPersistentProperties2.h"
+#include "nsIPlaintextEditor.h"
+#include "nsFrameSelection.h"
+
+namespace mozilla {
+namespace a11y {
+
+inline bool
+HyperTextAccessible::IsValidOffset(int32_t aOffset)
+{
+ index_t offset = ConvertMagicOffset(aOffset);
+ return offset.IsValid() && offset <= CharacterCount();
+}
+
+inline bool
+HyperTextAccessible::IsValidRange(int32_t aStartOffset, int32_t aEndOffset)
+{
+ index_t startOffset = ConvertMagicOffset(aStartOffset);
+ index_t endOffset = ConvertMagicOffset(aEndOffset);
+ return startOffset.IsValid() && endOffset.IsValid() &&
+ startOffset <= endOffset && endOffset <= CharacterCount();
+}
+
+inline void
+HyperTextAccessible::SetCaretOffset(int32_t aOffset)
+{
+ SetSelectionRange(aOffset, aOffset);
+ // XXX: Force cache refresh until a good solution for AT emulation of user
+ // input is implemented (AccessFu caret movement).
+ SelectionMgr()->UpdateCaretOffset(this, aOffset);
+}
+
+inline bool
+HyperTextAccessible::AddToSelection(int32_t aStartOffset, int32_t aEndOffset)
+{
+ dom::Selection* domSel = DOMSelection();
+ return domSel &&
+ SetSelectionBoundsAt(domSel->RangeCount(), aStartOffset, aEndOffset);
+}
+
+inline void
+HyperTextAccessible::ReplaceText(const nsAString& aText)
+{
+ // We need to call DeleteText() even if there is no contents because we need
+ // to ensure to move focus to the editor via SetSelectionRange() called in
+ // DeleteText().
+ DeleteText(0, CharacterCount());
+
+ nsCOMPtr<nsIEditor> editor = GetEditor();
+ nsCOMPtr<nsIPlaintextEditor> plaintextEditor(do_QueryInterface(editor));
+ if (!plaintextEditor) {
+ return;
+ }
+
+ // DeleteText() may cause inserting <br> element in some cases. Let's
+ // select all again and replace whole contents.
+ editor->SelectAll();
+
+ plaintextEditor->InsertText(aText);
+}
+
+inline void
+HyperTextAccessible::InsertText(const nsAString& aText, int32_t aPosition)
+{
+ nsCOMPtr<nsIEditor> editor = GetEditor();
+ nsCOMPtr<nsIPlaintextEditor> peditor(do_QueryInterface(editor));
+ if (peditor) {
+ SetSelectionRange(aPosition, aPosition);
+ peditor->InsertText(aText);
+ }
+}
+
+inline void
+HyperTextAccessible::CopyText(int32_t aStartPos, int32_t aEndPos)
+ {
+ nsCOMPtr<nsIEditor> editor = GetEditor();
+ if (editor) {
+ SetSelectionRange(aStartPos, aEndPos);
+ editor->Copy();
+ }
+ }
+
+inline void
+HyperTextAccessible::CutText(int32_t aStartPos, int32_t aEndPos)
+ {
+ nsCOMPtr<nsIEditor> editor = GetEditor();
+ if (editor) {
+ SetSelectionRange(aStartPos, aEndPos);
+ editor->Cut();
+ }
+ }
+
+inline void
+HyperTextAccessible::DeleteText(int32_t aStartPos, int32_t aEndPos)
+{
+ nsCOMPtr<nsIEditor> editor = GetEditor();
+ if (editor) {
+ SetSelectionRange(aStartPos, aEndPos);
+ editor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
+ }
+}
+
+inline void
+HyperTextAccessible::PasteText(int32_t aPosition)
+{
+ nsCOMPtr<nsIEditor> editor = GetEditor();
+ if (editor) {
+ SetSelectionRange(aPosition, aPosition);
+ editor->Paste(nsIClipboard::kGlobalClipboard);
+ }
+}
+
+inline index_t
+HyperTextAccessible::ConvertMagicOffset(int32_t aOffset) const
+{
+ if (aOffset == nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT)
+ return CharacterCount();
+
+ if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
+ return CaretOffset();
+
+ return aOffset;
+}
+
+inline uint32_t
+HyperTextAccessible::AdjustCaretOffset(uint32_t aOffset) const
+{
+ // It is the same character offset when the caret is visually at the very
+ // end of a line or the start of a new line (soft line break). Getting text
+ // at the line should provide the line with the visual caret, otherwise
+ // screen readers will announce the wrong line as the user presses up or
+ // down arrow and land at the end of a line.
+ if (aOffset > 0 && IsCaretAtEndOfLine())
+ return aOffset - 1;
+
+ return aOffset;
+}
+
+inline bool
+HyperTextAccessible::IsCaretAtEndOfLine() const
+{
+ RefPtr<nsFrameSelection> frameSelection = FrameSelection();
+ return frameSelection &&
+ frameSelection->GetHint() == CARET_ASSOCIATE_BEFORE;
+}
+
+inline already_AddRefed<nsFrameSelection>
+HyperTextAccessible::FrameSelection() const
+{
+ nsIFrame* frame = GetFrame();
+ return frame ? frame->GetFrameSelection() : nullptr;
+}
+
+inline dom::Selection*
+HyperTextAccessible::DOMSelection() const
+{
+ RefPtr<nsFrameSelection> frameSelection = FrameSelection();
+ return frameSelection ? frameSelection->GetSelection(SelectionType::eNormal) :
+ nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/generic/HyperTextAccessible.cpp b/accessible/generic/HyperTextAccessible.cpp
new file mode 100644
index 000000000..059c27372
--- /dev/null
+++ b/accessible/generic/HyperTextAccessible.cpp
@@ -0,0 +1,2230 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=78: */
+/* 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 "HyperTextAccessible-inl.h"
+
+#include "Accessible-inl.h"
+#include "nsAccessibilityService.h"
+#include "nsIAccessibleTypes.h"
+#include "DocAccessible.h"
+#include "HTMLListAccessible.h"
+#include "Role.h"
+#include "States.h"
+#include "TextAttrs.h"
+#include "TextRange.h"
+#include "TreeWalker.h"
+
+#include "nsCaret.h"
+#include "nsContentUtils.h"
+#include "nsFocusManager.h"
+#include "nsIDOMRange.h"
+#include "nsIEditingSession.h"
+#include "nsContainerFrame.h"
+#include "nsFrameSelection.h"
+#include "nsILineIterator.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIPersistentProperties2.h"
+#include "nsIScrollableFrame.h"
+#include "nsIServiceManager.h"
+#include "nsITextControlElement.h"
+#include "nsIMathMLFrame.h"
+#include "nsTextFragment.h"
+#include "mozilla/BinarySearch.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/MathAlgorithms.h"
+#include "gfxSkipChars.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// HyperTextAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HyperTextAccessible::
+ HyperTextAccessible(nsIContent* aNode, DocAccessible* aDoc) :
+ AccessibleWrap(aNode, aDoc)
+{
+ mType = eHyperTextType;
+ mGenericTypes |= eHyperText;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(HyperTextAccessible, Accessible)
+
+role
+HyperTextAccessible::NativeRole()
+{
+ a11y::role r = GetAccService()->MarkupRole(mContent);
+ if (r != roles::NOTHING)
+ return r;
+
+ nsIFrame* frame = GetFrame();
+ if (frame && frame->GetType() == nsGkAtoms::inlineFrame)
+ return roles::TEXT;
+
+ return roles::TEXT_CONTAINER;
+}
+
+uint64_t
+HyperTextAccessible::NativeState()
+{
+ uint64_t states = AccessibleWrap::NativeState();
+
+ if (mContent->AsElement()->State().HasState(NS_EVENT_STATE_MOZ_READWRITE)) {
+ states |= states::EDITABLE;
+
+ } else if (mContent->IsHTMLElement(nsGkAtoms::article)) {
+ // We want <article> to behave like a document in terms of readonly state.
+ states |= states::READONLY;
+ }
+
+ if (HasChildren())
+ states |= states::SELECTABLE_TEXT;
+
+ return states;
+}
+
+nsIntRect
+HyperTextAccessible::GetBoundsInFrame(nsIFrame* aFrame,
+ uint32_t aStartRenderedOffset,
+ uint32_t aEndRenderedOffset)
+{
+ nsPresContext* presContext = mDoc->PresContext();
+ if (aFrame->GetType() != nsGkAtoms::textFrame) {
+ return aFrame->GetScreenRectInAppUnits().
+ ToNearestPixels(presContext->AppUnitsPerDevPixel());
+ }
+
+ // Substring must be entirely within the same text node.
+ int32_t startContentOffset, endContentOffset;
+ nsresult rv = RenderedToContentOffset(aFrame, aStartRenderedOffset, &startContentOffset);
+ NS_ENSURE_SUCCESS(rv, nsIntRect());
+ rv = RenderedToContentOffset(aFrame, aEndRenderedOffset, &endContentOffset);
+ NS_ENSURE_SUCCESS(rv, nsIntRect());
+
+ nsIFrame *frame;
+ int32_t startContentOffsetInFrame;
+ // Get the right frame continuation -- not really a child, but a sibling of
+ // the primary frame passed in
+ rv = aFrame->GetChildFrameContainingOffset(startContentOffset, false,
+ &startContentOffsetInFrame, &frame);
+ NS_ENSURE_SUCCESS(rv, nsIntRect());
+
+ nsRect screenRect;
+ while (frame && startContentOffset < endContentOffset) {
+ // Start with this frame's screen rect, which we will shrink based on
+ // the substring we care about within it. We will then add that frame to
+ // the total screenRect we are returning.
+ nsRect frameScreenRect = frame->GetScreenRectInAppUnits();
+
+ // Get the length of the substring in this frame that we want the bounds for
+ int32_t startFrameTextOffset, endFrameTextOffset;
+ frame->GetOffsets(startFrameTextOffset, endFrameTextOffset);
+ int32_t frameTotalTextLength = endFrameTextOffset - startFrameTextOffset;
+ int32_t seekLength = endContentOffset - startContentOffset;
+ int32_t frameSubStringLength = std::min(frameTotalTextLength - startContentOffsetInFrame, seekLength);
+
+ // Add the point where the string starts to the frameScreenRect
+ nsPoint frameTextStartPoint;
+ rv = frame->GetPointFromOffset(startContentOffset, &frameTextStartPoint);
+ NS_ENSURE_SUCCESS(rv, nsIntRect());
+
+ // Use the point for the end offset to calculate the width
+ nsPoint frameTextEndPoint;
+ rv = frame->GetPointFromOffset(startContentOffset + frameSubStringLength, &frameTextEndPoint);
+ NS_ENSURE_SUCCESS(rv, nsIntRect());
+
+ frameScreenRect.x += std::min(frameTextStartPoint.x, frameTextEndPoint.x);
+ frameScreenRect.width = mozilla::Abs(frameTextStartPoint.x - frameTextEndPoint.x);
+
+ screenRect.UnionRect(frameScreenRect, screenRect);
+
+ // Get ready to loop back for next frame continuation
+ startContentOffset += frameSubStringLength;
+ startContentOffsetInFrame = 0;
+ frame = frame->GetNextContinuation();
+ }
+
+ return screenRect.ToNearestPixels(presContext->AppUnitsPerDevPixel());
+}
+
+void
+HyperTextAccessible::TextSubstring(int32_t aStartOffset, int32_t aEndOffset,
+ nsAString& aText)
+{
+ aText.Truncate();
+
+ index_t startOffset = ConvertMagicOffset(aStartOffset);
+ index_t endOffset = ConvertMagicOffset(aEndOffset);
+ if (!startOffset.IsValid() || !endOffset.IsValid() ||
+ startOffset > endOffset || endOffset > CharacterCount()) {
+ NS_ERROR("Wrong in offset");
+ return;
+ }
+
+ int32_t startChildIdx = GetChildIndexAtOffset(startOffset);
+ if (startChildIdx == -1)
+ return;
+
+ int32_t endChildIdx = GetChildIndexAtOffset(endOffset);
+ if (endChildIdx == -1)
+ return;
+
+ if (startChildIdx == endChildIdx) {
+ int32_t childOffset = GetChildOffset(startChildIdx);
+ if (childOffset == -1)
+ return;
+
+ Accessible* child = GetChildAt(startChildIdx);
+ child->AppendTextTo(aText, startOffset - childOffset,
+ endOffset - startOffset);
+ return;
+ }
+
+ int32_t startChildOffset = GetChildOffset(startChildIdx);
+ if (startChildOffset == -1)
+ return;
+
+ Accessible* startChild = GetChildAt(startChildIdx);
+ startChild->AppendTextTo(aText, startOffset - startChildOffset);
+
+ for (int32_t childIdx = startChildIdx + 1; childIdx < endChildIdx; childIdx++) {
+ Accessible* child = GetChildAt(childIdx);
+ child->AppendTextTo(aText);
+ }
+
+ int32_t endChildOffset = GetChildOffset(endChildIdx);
+ if (endChildOffset == -1)
+ return;
+
+ Accessible* endChild = GetChildAt(endChildIdx);
+ endChild->AppendTextTo(aText, 0, endOffset - endChildOffset);
+}
+
+uint32_t
+HyperTextAccessible::DOMPointToOffset(nsINode* aNode, int32_t aNodeOffset,
+ bool aIsEndOffset) const
+{
+ if (!aNode)
+ return 0;
+
+ uint32_t offset = 0;
+ nsINode* findNode = nullptr;
+
+ if (aNodeOffset == -1) {
+ findNode = aNode;
+
+ } else if (aNode->IsNodeOfType(nsINode::eTEXT)) {
+ // For text nodes, aNodeOffset comes in as a character offset
+ // Text offset will be added at the end, if we find the offset in this hypertext
+ // We want the "skipped" offset into the text (rendered text without the extra whitespace)
+ nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
+ NS_ENSURE_TRUE(frame, 0);
+
+ nsresult rv = ContentToRenderedOffset(frame, aNodeOffset, &offset);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ findNode = aNode;
+
+ } else {
+ // findNode could be null if aNodeOffset == # of child nodes, which means
+ // one of two things:
+ // 1) there are no children, and the passed-in node is not mContent -- use
+ // parentContent for the node to find
+ // 2) there are no children and the passed-in node is mContent, which means
+ // we're an empty nsIAccessibleText
+ // 3) there are children and we're at the end of the children
+
+ findNode = aNode->GetChildAt(aNodeOffset);
+ if (!findNode) {
+ if (aNodeOffset == 0) {
+ if (aNode == GetNode()) {
+ // Case #1: this accessible has no children and thus has empty text,
+ // we can only be at hypertext offset 0.
+ return 0;
+ }
+
+ // Case #2: there are no children, we're at this node.
+ findNode = aNode;
+ } else if (aNodeOffset == static_cast<int32_t>(aNode->GetChildCount())) {
+ // Case #3: we're after the last child, get next node to this one.
+ for (nsINode* tmpNode = aNode;
+ !findNode && tmpNode && tmpNode != mContent;
+ tmpNode = tmpNode->GetParent()) {
+ findNode = tmpNode->GetNextSibling();
+ }
+ }
+ }
+ }
+
+ // Get accessible for this findNode, or if that node isn't accessible, use the
+ // accessible for the next DOM node which has one (based on forward depth first search)
+ Accessible* descendant = nullptr;
+ if (findNode) {
+ nsCOMPtr<nsIContent> findContent(do_QueryInterface(findNode));
+ if (findContent && findContent->IsHTMLElement() &&
+ findContent->NodeInfo()->Equals(nsGkAtoms::br) &&
+ findContent->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::mozeditorbogusnode,
+ nsGkAtoms::_true,
+ eIgnoreCase)) {
+ // This <br> is the hacky "bogus node" used when there is no text in a control
+ return 0;
+ }
+
+ descendant = mDoc->GetAccessible(findNode);
+ if (!descendant && findNode->IsContent()) {
+ Accessible* container = mDoc->GetContainerAccessible(findNode);
+ if (container) {
+ TreeWalker walker(container, findNode->AsContent(),
+ TreeWalker::eWalkContextTree);
+ descendant = walker.Next();
+ if (!descendant)
+ descendant = container;
+ }
+ }
+ }
+
+ return TransformOffset(descendant, offset, aIsEndOffset);
+}
+
+uint32_t
+HyperTextAccessible::TransformOffset(Accessible* aDescendant,
+ uint32_t aOffset, bool aIsEndOffset) const
+{
+ // From the descendant, go up and get the immediate child of this hypertext.
+ uint32_t offset = aOffset;
+ Accessible* descendant = aDescendant;
+ while (descendant) {
+ Accessible* parent = descendant->Parent();
+ if (parent == this)
+ return GetChildOffset(descendant) + offset;
+
+ // This offset no longer applies because the passed-in text object is not
+ // a child of the hypertext. This happens when there are nested hypertexts,
+ // e.g. <div>abc<h1>def</h1>ghi</div>. Thus we need to adjust the offset
+ // to make it relative the hypertext.
+ // If the end offset is not supposed to be inclusive and the original point
+ // is not at 0 offset then the returned offset should be after an embedded
+ // character the original point belongs to.
+ if (aIsEndOffset)
+ offset = (offset > 0 || descendant->IndexInParent() > 0) ? 1 : 0;
+ else
+ offset = 0;
+
+ descendant = parent;
+ }
+
+ // If the given a11y point cannot be mapped into offset relative this hypertext
+ // offset then return length as fallback value.
+ return CharacterCount();
+}
+
+/**
+ * GetElementAsContentOf() returns a content representing an element which is
+ * or includes aNode.
+ *
+ * XXX This method is enough to retrieve ::before or ::after pseudo element.
+ * So, if you want to use this for other purpose, you might need to check
+ * ancestors too.
+ */
+static nsIContent* GetElementAsContentOf(nsINode* aNode)
+{
+ if (aNode->IsElement()) {
+ return aNode->AsContent();
+ }
+ nsIContent* parent = aNode->GetParent();
+ return parent && parent->IsElement() ? parent : nullptr;
+}
+
+bool
+HyperTextAccessible::OffsetsToDOMRange(int32_t aStartOffset, int32_t aEndOffset,
+ nsRange* aRange)
+{
+ DOMPoint startPoint = OffsetToDOMPoint(aStartOffset);
+ if (!startPoint.node)
+ return false;
+
+ // HyperTextAccessible manages pseudo elements generated by ::before or
+ // ::after. However, contents of them are not in the DOM tree normally.
+ // Therefore, they are not selectable and editable. So, when this creates
+ // a DOM range, it should not start from nor end in any pseudo contents.
+
+ nsIContent* container = GetElementAsContentOf(startPoint.node);
+ DOMPoint startPointForDOMRange =
+ ClosestNotGeneratedDOMPoint(startPoint, container);
+ aRange->SetStart(startPointForDOMRange.node, startPointForDOMRange.idx);
+
+ // If the caller wants collapsed range, let's collapse the range to its start.
+ if (aStartOffset == aEndOffset) {
+ aRange->Collapse(true);
+ return true;
+ }
+
+ DOMPoint endPoint = OffsetToDOMPoint(aEndOffset);
+ if (!endPoint.node)
+ return false;
+
+ if (startPoint.node != endPoint.node) {
+ container = GetElementAsContentOf(endPoint.node);
+ }
+
+ DOMPoint endPointForDOMRange =
+ ClosestNotGeneratedDOMPoint(endPoint, container);
+ aRange->SetEnd(endPointForDOMRange.node, endPointForDOMRange.idx);
+ return true;
+}
+
+DOMPoint
+HyperTextAccessible::OffsetToDOMPoint(int32_t aOffset)
+{
+ // 0 offset is valid even if no children. In this case the associated editor
+ // is empty so return a DOM point for editor root element.
+ if (aOffset == 0) {
+ nsCOMPtr<nsIEditor> editor = GetEditor();
+ if (editor) {
+ bool isEmpty = false;
+ editor->GetDocumentIsEmpty(&isEmpty);
+ if (isEmpty) {
+ nsCOMPtr<nsIDOMElement> editorRootElm;
+ editor->GetRootElement(getter_AddRefs(editorRootElm));
+
+ nsCOMPtr<nsINode> editorRoot(do_QueryInterface(editorRootElm));
+ return DOMPoint(editorRoot, 0);
+ }
+ }
+ }
+
+ int32_t childIdx = GetChildIndexAtOffset(aOffset);
+ if (childIdx == -1)
+ return DOMPoint();
+
+ Accessible* child = GetChildAt(childIdx);
+ int32_t innerOffset = aOffset - GetChildOffset(childIdx);
+
+ // A text leaf case.
+ if (child->IsTextLeaf()) {
+ // The point is inside the text node. This is always true for any text leaf
+ // except a last child one. See assertion below.
+ if (aOffset < GetChildOffset(childIdx + 1)) {
+ nsIContent* content = child->GetContent();
+ int32_t idx = 0;
+ if (NS_FAILED(RenderedToContentOffset(content->GetPrimaryFrame(),
+ innerOffset, &idx)))
+ return DOMPoint();
+
+ return DOMPoint(content, idx);
+ }
+
+ // Set the DOM point right after the text node.
+ MOZ_ASSERT(static_cast<uint32_t>(aOffset) == CharacterCount());
+ innerOffset = 1;
+ }
+
+ // Case of embedded object. The point is either before or after the element.
+ NS_ASSERTION(innerOffset == 0 || innerOffset == 1, "A wrong inner offset!");
+ nsINode* node = child->GetNode();
+ nsINode* parentNode = node->GetParentNode();
+ return parentNode ?
+ DOMPoint(parentNode, parentNode->IndexOf(node) + innerOffset) :
+ DOMPoint();
+}
+
+DOMPoint
+HyperTextAccessible::ClosestNotGeneratedDOMPoint(const DOMPoint& aDOMPoint,
+ nsIContent* aElementContent)
+{
+ MOZ_ASSERT(aDOMPoint.node, "The node must not be null");
+
+ // ::before pseudo element
+ if (aElementContent &&
+ aElementContent->IsGeneratedContentContainerForBefore()) {
+ MOZ_ASSERT(aElementContent->GetParent(),
+ "::before must have parent element");
+ // The first child of its parent (i.e., immediately after the ::before) is
+ // good point for a DOM range.
+ return DOMPoint(aElementContent->GetParent(), 0);
+ }
+
+ // ::after pseudo element
+ if (aElementContent &&
+ aElementContent->IsGeneratedContentContainerForAfter()) {
+ MOZ_ASSERT(aElementContent->GetParent(),
+ "::after must have parent element");
+ // The end of its parent (i.e., immediately before the ::after) is good
+ // point for a DOM range.
+ return DOMPoint(aElementContent->GetParent(),
+ aElementContent->GetParent()->GetChildCount());
+ }
+
+ return aDOMPoint;
+}
+
+uint32_t
+HyperTextAccessible::FindOffset(uint32_t aOffset, nsDirection aDirection,
+ nsSelectionAmount aAmount,
+ EWordMovementType aWordMovementType)
+{
+ NS_ASSERTION(aDirection == eDirPrevious || aAmount != eSelectBeginLine,
+ "eSelectBeginLine should only be used with eDirPrevious");
+
+ // Find a leaf accessible frame to start with. PeekOffset wants this.
+ HyperTextAccessible* text = this;
+ Accessible* child = nullptr;
+ int32_t innerOffset = aOffset;
+
+ do {
+ int32_t childIdx = text->GetChildIndexAtOffset(innerOffset);
+
+ // We can have an empty text leaf as our only child. Since empty text
+ // leaves are not accessible we then have no children, but 0 is a valid
+ // innerOffset.
+ if (childIdx == -1) {
+ NS_ASSERTION(innerOffset == 0 && !text->ChildCount(), "No childIdx?");
+ return DOMPointToOffset(text->GetNode(), 0, aDirection == eDirNext);
+ }
+
+ child = text->GetChildAt(childIdx);
+
+ // HTML list items may need special processing because PeekOffset doesn't
+ // work with list bullets.
+ if (text->IsHTMLListItem()) {
+ HTMLLIAccessible* li = text->AsHTMLListItem();
+ if (child == li->Bullet()) {
+ // XXX: the logic is broken for multichar bullets in moving by
+ // char/cluster/word cases.
+ if (text != this) {
+ return aDirection == eDirPrevious ?
+ TransformOffset(text, 0, false) :
+ TransformOffset(text, 1, true);
+ }
+ if (aDirection == eDirPrevious)
+ return 0;
+
+ uint32_t nextOffset = GetChildOffset(1);
+ if (nextOffset == 0)
+ return 0;
+
+ switch (aAmount) {
+ case eSelectLine:
+ case eSelectEndLine:
+ // Ask a text leaf next (if not empty) to the bullet for an offset
+ // since list item may be multiline.
+ return nextOffset < CharacterCount() ?
+ FindOffset(nextOffset, aDirection, aAmount, aWordMovementType) :
+ nextOffset;
+
+ default:
+ return nextOffset;
+ }
+ }
+ }
+
+ innerOffset -= text->GetChildOffset(childIdx);
+
+ text = child->AsHyperText();
+ } while (text);
+
+ nsIFrame* childFrame = child->GetFrame();
+ if (!childFrame) {
+ NS_ERROR("No child frame");
+ return 0;
+ }
+
+ int32_t innerContentOffset = innerOffset;
+ if (child->IsTextLeaf()) {
+ NS_ASSERTION(childFrame->GetType() == nsGkAtoms::textFrame, "Wrong frame!");
+ RenderedToContentOffset(childFrame, innerOffset, &innerContentOffset);
+ }
+
+ nsIFrame* frameAtOffset = childFrame;
+ int32_t unusedOffsetInFrame = 0;
+ childFrame->GetChildFrameContainingOffset(innerContentOffset, true,
+ &unusedOffsetInFrame,
+ &frameAtOffset);
+
+ const bool kIsJumpLinesOk = true; // okay to jump lines
+ const bool kIsScrollViewAStop = false; // do not stop at scroll views
+ const bool kIsKeyboardSelect = true; // is keyboard selection
+ const bool kIsVisualBidi = false; // use visual order for bidi text
+ nsPeekOffsetStruct pos(aAmount, aDirection, innerContentOffset,
+ nsPoint(0, 0), kIsJumpLinesOk, kIsScrollViewAStop,
+ kIsKeyboardSelect, kIsVisualBidi,
+ false, aWordMovementType);
+ nsresult rv = frameAtOffset->PeekOffset(&pos);
+
+ // PeekOffset fails on last/first lines of the text in certain cases.
+ if (NS_FAILED(rv) && aAmount == eSelectLine) {
+ pos.mAmount = (aDirection == eDirNext) ? eSelectEndLine : eSelectBeginLine;
+ frameAtOffset->PeekOffset(&pos);
+ }
+ if (!pos.mResultContent) {
+ NS_ERROR("No result content!");
+ return 0;
+ }
+
+ // Turn the resulting DOM point into an offset.
+ uint32_t hyperTextOffset = DOMPointToOffset(pos.mResultContent,
+ pos.mContentOffset,
+ aDirection == eDirNext);
+
+ if (aDirection == eDirPrevious) {
+ // If we reached the end during search, this means we didn't find the DOM point
+ // and we're actually at the start of the paragraph
+ if (hyperTextOffset == CharacterCount())
+ return 0;
+
+ // PeekOffset stops right before bullet so return 0 to workaround it.
+ if (IsHTMLListItem() && aAmount == eSelectBeginLine &&
+ hyperTextOffset > 0) {
+ Accessible* prevOffsetChild = GetChildAtOffset(hyperTextOffset - 1);
+ if (prevOffsetChild == AsHTMLListItem()->Bullet())
+ return 0;
+ }
+ }
+
+ return hyperTextOffset;
+}
+
+uint32_t
+HyperTextAccessible::FindLineBoundary(uint32_t aOffset,
+ EWhichLineBoundary aWhichLineBoundary)
+{
+ // Note: empty last line doesn't have own frame (a previous line contains '\n'
+ // character instead) thus when it makes a difference we need to process this
+ // case separately (otherwise operations are performed on previous line).
+ switch (aWhichLineBoundary) {
+ case ePrevLineBegin: {
+ // Fetch a previous line and move to its start (as arrow up and home keys
+ // were pressed).
+ if (IsEmptyLastLineOffset(aOffset))
+ return FindOffset(aOffset, eDirPrevious, eSelectBeginLine);
+
+ uint32_t tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectLine);
+ return FindOffset(tmpOffset, eDirPrevious, eSelectBeginLine);
+ }
+
+ case ePrevLineEnd: {
+ if (IsEmptyLastLineOffset(aOffset))
+ return aOffset - 1;
+
+ // If offset is at first line then return 0 (first line start).
+ uint32_t tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectBeginLine);
+ if (tmpOffset == 0)
+ return 0;
+
+ // Otherwise move to end of previous line (as arrow up and end keys were
+ // pressed).
+ tmpOffset = FindOffset(aOffset, eDirPrevious, eSelectLine);
+ return FindOffset(tmpOffset, eDirNext, eSelectEndLine);
+ }
+
+ case eThisLineBegin:
+ if (IsEmptyLastLineOffset(aOffset))
+ return aOffset;
+
+ // Move to begin of the current line (as home key was pressed).
+ return FindOffset(aOffset, eDirPrevious, eSelectBeginLine);
+
+ case eThisLineEnd:
+ if (IsEmptyLastLineOffset(aOffset))
+ return aOffset;
+
+ // Move to end of the current line (as end key was pressed).
+ return FindOffset(aOffset, eDirNext, eSelectEndLine);
+
+ case eNextLineBegin: {
+ if (IsEmptyLastLineOffset(aOffset))
+ return aOffset;
+
+ // Move to begin of the next line if any (arrow down and home keys),
+ // otherwise end of the current line (arrow down only).
+ uint32_t tmpOffset = FindOffset(aOffset, eDirNext, eSelectLine);
+ if (tmpOffset == CharacterCount())
+ return tmpOffset;
+
+ return FindOffset(tmpOffset, eDirPrevious, eSelectBeginLine);
+ }
+
+ case eNextLineEnd: {
+ if (IsEmptyLastLineOffset(aOffset))
+ return aOffset;
+
+ // Move to next line end (as down arrow and end key were pressed).
+ uint32_t tmpOffset = FindOffset(aOffset, eDirNext, eSelectLine);
+ if (tmpOffset == CharacterCount())
+ return tmpOffset;
+
+ return FindOffset(tmpOffset, eDirNext, eSelectEndLine);
+ }
+ }
+
+ return 0;
+}
+
+void
+HyperTextAccessible::TextBeforeOffset(int32_t aOffset,
+ AccessibleTextBoundary aBoundaryType,
+ int32_t* aStartOffset, int32_t* aEndOffset,
+ nsAString& aText)
+{
+ *aStartOffset = *aEndOffset = 0;
+ aText.Truncate();
+
+ index_t convertedOffset = ConvertMagicOffset(aOffset);
+ if (!convertedOffset.IsValid() || convertedOffset > CharacterCount()) {
+ NS_ERROR("Wrong in offset!");
+ return;
+ }
+
+ uint32_t adjustedOffset = convertedOffset;
+ if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
+ adjustedOffset = AdjustCaretOffset(adjustedOffset);
+
+ switch (aBoundaryType) {
+ case nsIAccessibleText::BOUNDARY_CHAR:
+ if (convertedOffset != 0)
+ CharAt(convertedOffset - 1, aText, aStartOffset, aEndOffset);
+ break;
+
+ case nsIAccessibleText::BOUNDARY_WORD_START: {
+ // If the offset is a word start (except text length offset) then move
+ // backward to find a start offset (end offset is the given offset).
+ // Otherwise move backward twice to find both start and end offsets.
+ if (adjustedOffset == CharacterCount()) {
+ *aEndOffset = FindWordBoundary(adjustedOffset, eDirPrevious, eStartWord);
+ *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord);
+ } else {
+ *aStartOffset = FindWordBoundary(adjustedOffset, eDirPrevious, eStartWord);
+ *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eStartWord);
+ if (*aEndOffset != static_cast<int32_t>(adjustedOffset)) {
+ *aEndOffset = *aStartOffset;
+ *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord);
+ }
+ }
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+ break;
+ }
+
+ case nsIAccessibleText::BOUNDARY_WORD_END: {
+ // Move word backward twice to find start and end offsets.
+ *aEndOffset = FindWordBoundary(convertedOffset, eDirPrevious, eEndWord);
+ *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord);
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+ break;
+ }
+
+ case nsIAccessibleText::BOUNDARY_LINE_START:
+ *aStartOffset = FindLineBoundary(adjustedOffset, ePrevLineBegin);
+ *aEndOffset = FindLineBoundary(adjustedOffset, eThisLineBegin);
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+ break;
+
+ case nsIAccessibleText::BOUNDARY_LINE_END: {
+ *aEndOffset = FindLineBoundary(adjustedOffset, ePrevLineEnd);
+ int32_t tmpOffset = *aEndOffset;
+ // Adjust offset if line is wrapped.
+ if (*aEndOffset != 0 && !IsLineEndCharAt(*aEndOffset))
+ tmpOffset--;
+
+ *aStartOffset = FindLineBoundary(tmpOffset, ePrevLineEnd);
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+ break;
+ }
+ }
+}
+
+void
+HyperTextAccessible::TextAtOffset(int32_t aOffset,
+ AccessibleTextBoundary aBoundaryType,
+ int32_t* aStartOffset, int32_t* aEndOffset,
+ nsAString& aText)
+{
+ *aStartOffset = *aEndOffset = 0;
+ aText.Truncate();
+
+ uint32_t adjustedOffset = ConvertMagicOffset(aOffset);
+ if (adjustedOffset == std::numeric_limits<uint32_t>::max()) {
+ NS_ERROR("Wrong given offset!");
+ return;
+ }
+
+ switch (aBoundaryType) {
+ case nsIAccessibleText::BOUNDARY_CHAR:
+ // Return no char if caret is at the end of wrapped line (case of no line
+ // end character). Returning a next line char is confusing for AT.
+ if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET && IsCaretAtEndOfLine())
+ *aStartOffset = *aEndOffset = adjustedOffset;
+ else
+ CharAt(adjustedOffset, aText, aStartOffset, aEndOffset);
+ break;
+
+ case nsIAccessibleText::BOUNDARY_WORD_START:
+ if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
+ adjustedOffset = AdjustCaretOffset(adjustedOffset);
+
+ *aEndOffset = FindWordBoundary(adjustedOffset, eDirNext, eStartWord);
+ *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eStartWord);
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+ break;
+
+ case nsIAccessibleText::BOUNDARY_WORD_END:
+ // Ignore the spec and follow what WebKitGtk does because Orca expects it,
+ // i.e. return a next word at word end offset of the current word
+ // (WebKitGtk behavior) instead the current word (AKT spec).
+ *aEndOffset = FindWordBoundary(adjustedOffset, eDirNext, eEndWord);
+ *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord);
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+ break;
+
+ case nsIAccessibleText::BOUNDARY_LINE_START:
+ if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
+ adjustedOffset = AdjustCaretOffset(adjustedOffset);
+
+ *aStartOffset = FindLineBoundary(adjustedOffset, eThisLineBegin);
+ *aEndOffset = FindLineBoundary(adjustedOffset, eNextLineBegin);
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+ break;
+
+ case nsIAccessibleText::BOUNDARY_LINE_END:
+ if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
+ adjustedOffset = AdjustCaretOffset(adjustedOffset);
+
+ // In contrast to word end boundary we follow the spec here.
+ *aStartOffset = FindLineBoundary(adjustedOffset, ePrevLineEnd);
+ *aEndOffset = FindLineBoundary(adjustedOffset, eThisLineEnd);
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+ break;
+ }
+}
+
+void
+HyperTextAccessible::TextAfterOffset(int32_t aOffset,
+ AccessibleTextBoundary aBoundaryType,
+ int32_t* aStartOffset, int32_t* aEndOffset,
+ nsAString& aText)
+{
+ *aStartOffset = *aEndOffset = 0;
+ aText.Truncate();
+
+ index_t convertedOffset = ConvertMagicOffset(aOffset);
+ if (!convertedOffset.IsValid() || convertedOffset > CharacterCount()) {
+ NS_ERROR("Wrong in offset!");
+ return;
+ }
+
+ uint32_t adjustedOffset = convertedOffset;
+ if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
+ adjustedOffset = AdjustCaretOffset(adjustedOffset);
+
+ switch (aBoundaryType) {
+ case nsIAccessibleText::BOUNDARY_CHAR:
+ // If caret is at the end of wrapped line (case of no line end character)
+ // then char after the offset is a first char at next line.
+ if (adjustedOffset >= CharacterCount())
+ *aStartOffset = *aEndOffset = CharacterCount();
+ else
+ CharAt(adjustedOffset + 1, aText, aStartOffset, aEndOffset);
+ break;
+
+ case nsIAccessibleText::BOUNDARY_WORD_START:
+ // Move word forward twice to find start and end offsets.
+ *aStartOffset = FindWordBoundary(adjustedOffset, eDirNext, eStartWord);
+ *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eStartWord);
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+ break;
+
+ case nsIAccessibleText::BOUNDARY_WORD_END:
+ // If the offset is a word end (except 0 offset) then move forward to find
+ // end offset (start offset is the given offset). Otherwise move forward
+ // twice to find both start and end offsets.
+ if (convertedOffset == 0) {
+ *aStartOffset = FindWordBoundary(convertedOffset, eDirNext, eEndWord);
+ *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eEndWord);
+ } else {
+ *aEndOffset = FindWordBoundary(convertedOffset, eDirNext, eEndWord);
+ *aStartOffset = FindWordBoundary(*aEndOffset, eDirPrevious, eEndWord);
+ if (*aStartOffset != static_cast<int32_t>(convertedOffset)) {
+ *aStartOffset = *aEndOffset;
+ *aEndOffset = FindWordBoundary(*aStartOffset, eDirNext, eEndWord);
+ }
+ }
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+ break;
+
+ case nsIAccessibleText::BOUNDARY_LINE_START:
+ *aStartOffset = FindLineBoundary(adjustedOffset, eNextLineBegin);
+ *aEndOffset = FindLineBoundary(*aStartOffset, eNextLineBegin);
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+ break;
+
+ case nsIAccessibleText::BOUNDARY_LINE_END:
+ *aStartOffset = FindLineBoundary(adjustedOffset, eThisLineEnd);
+ *aEndOffset = FindLineBoundary(adjustedOffset, eNextLineEnd);
+ TextSubstring(*aStartOffset, *aEndOffset, aText);
+ break;
+ }
+}
+
+already_AddRefed<nsIPersistentProperties>
+HyperTextAccessible::TextAttributes(bool aIncludeDefAttrs, int32_t aOffset,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset)
+{
+ // 1. Get each attribute and its ranges one after another.
+ // 2. As we get each new attribute, we pass the current start and end offsets
+ // as in/out parameters. In other words, as attributes are collected,
+ // the attribute range itself can only stay the same or get smaller.
+
+ *aStartOffset = *aEndOffset = 0;
+ index_t offset = ConvertMagicOffset(aOffset);
+ if (!offset.IsValid() || offset > CharacterCount()) {
+ NS_ERROR("Wrong in offset!");
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID);
+
+ Accessible* accAtOffset = GetChildAtOffset(offset);
+ if (!accAtOffset) {
+ // Offset 0 is correct offset when accessible has empty text. Include
+ // default attributes if they were requested, otherwise return empty set.
+ if (offset == 0) {
+ if (aIncludeDefAttrs) {
+ TextAttrsMgr textAttrsMgr(this);
+ textAttrsMgr.GetAttributes(attributes);
+ }
+ return attributes.forget();
+ }
+ return nullptr;
+ }
+
+ int32_t accAtOffsetIdx = accAtOffset->IndexInParent();
+ uint32_t startOffset = GetChildOffset(accAtOffsetIdx);
+ uint32_t endOffset = GetChildOffset(accAtOffsetIdx + 1);
+ int32_t offsetInAcc = offset - startOffset;
+
+ TextAttrsMgr textAttrsMgr(this, aIncludeDefAttrs, accAtOffset,
+ accAtOffsetIdx);
+ textAttrsMgr.GetAttributes(attributes, &startOffset, &endOffset);
+
+ // Compute spelling attributes on text accessible only.
+ nsIFrame *offsetFrame = accAtOffset->GetFrame();
+ if (offsetFrame && offsetFrame->GetType() == nsGkAtoms::textFrame) {
+ int32_t nodeOffset = 0;
+ RenderedToContentOffset(offsetFrame, offsetInAcc, &nodeOffset);
+
+ // Set 'misspelled' text attribute.
+ GetSpellTextAttr(accAtOffset->GetNode(), nodeOffset,
+ &startOffset, &endOffset, attributes);
+ }
+
+ *aStartOffset = startOffset;
+ *aEndOffset = endOffset;
+ return attributes.forget();
+}
+
+already_AddRefed<nsIPersistentProperties>
+HyperTextAccessible::DefaultTextAttributes()
+{
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID);
+
+ TextAttrsMgr textAttrsMgr(this);
+ textAttrsMgr.GetAttributes(attributes);
+ return attributes.forget();
+}
+
+int32_t
+HyperTextAccessible::GetLevelInternal()
+{
+ if (mContent->IsHTMLElement(nsGkAtoms::h1))
+ return 1;
+ if (mContent->IsHTMLElement(nsGkAtoms::h2))
+ return 2;
+ if (mContent->IsHTMLElement(nsGkAtoms::h3))
+ return 3;
+ if (mContent->IsHTMLElement(nsGkAtoms::h4))
+ return 4;
+ if (mContent->IsHTMLElement(nsGkAtoms::h5))
+ return 5;
+ if (mContent->IsHTMLElement(nsGkAtoms::h6))
+ return 6;
+
+ return AccessibleWrap::GetLevelInternal();
+}
+
+void
+HyperTextAccessible::SetMathMLXMLRoles(nsIPersistentProperties* aAttributes)
+{
+ // Add MathML xmlroles based on the position inside the parent.
+ Accessible* parent = Parent();
+ if (parent) {
+ switch (parent->Role()) {
+ case roles::MATHML_CELL:
+ case roles::MATHML_ENCLOSED:
+ case roles::MATHML_ERROR:
+ case roles::MATHML_MATH:
+ case roles::MATHML_ROW:
+ case roles::MATHML_SQUARE_ROOT:
+ case roles::MATHML_STYLE:
+ if (Role() == roles::MATHML_OPERATOR) {
+ // This is an operator inside an <mrow> (or an inferred <mrow>).
+ // See http://www.w3.org/TR/MathML3/chapter3.html#presm.inferredmrow
+ // XXX We should probably do something similar for MATHML_FENCED, but
+ // operators do not appear in the accessible tree. See bug 1175747.
+ nsIMathMLFrame* mathMLFrame = do_QueryFrame(GetFrame());
+ if (mathMLFrame) {
+ nsEmbellishData embellishData;
+ mathMLFrame->GetEmbellishData(embellishData);
+ if (NS_MATHML_EMBELLISH_IS_FENCE(embellishData.flags)) {
+ if (!PrevSibling()) {
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ nsGkAtoms::open_fence);
+ } else if (!NextSibling()) {
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ nsGkAtoms::close_fence);
+ }
+ }
+ if (NS_MATHML_EMBELLISH_IS_SEPARATOR(embellishData.flags)) {
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ nsGkAtoms::separator_);
+ }
+ }
+ }
+ break;
+ case roles::MATHML_FRACTION:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ IndexInParent() == 0 ?
+ nsGkAtoms::numerator :
+ nsGkAtoms::denominator);
+ break;
+ case roles::MATHML_ROOT:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ IndexInParent() == 0 ? nsGkAtoms::base :
+ nsGkAtoms::root_index);
+ break;
+ case roles::MATHML_SUB:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ IndexInParent() == 0 ? nsGkAtoms::base :
+ nsGkAtoms::subscript);
+ break;
+ case roles::MATHML_SUP:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ IndexInParent() == 0 ? nsGkAtoms::base :
+ nsGkAtoms::superscript);
+ break;
+ case roles::MATHML_SUB_SUP: {
+ int32_t index = IndexInParent();
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ index == 0 ? nsGkAtoms::base :
+ (index == 1 ? nsGkAtoms::subscript :
+ nsGkAtoms::superscript));
+ } break;
+ case roles::MATHML_UNDER:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ IndexInParent() == 0 ? nsGkAtoms::base :
+ nsGkAtoms::underscript);
+ break;
+ case roles::MATHML_OVER:
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ IndexInParent() == 0 ? nsGkAtoms::base :
+ nsGkAtoms::overscript);
+ break;
+ case roles::MATHML_UNDER_OVER: {
+ int32_t index = IndexInParent();
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ index == 0 ? nsGkAtoms::base :
+ (index == 1 ? nsGkAtoms::underscript :
+ nsGkAtoms::overscript));
+ } break;
+ case roles::MATHML_MULTISCRIPTS: {
+ // Get the <multiscripts> base.
+ nsIContent* child;
+ bool baseFound = false;
+ for (child = parent->GetContent()->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsMathMLElement()) {
+ baseFound = true;
+ break;
+ }
+ }
+ if (baseFound) {
+ nsIContent* content = GetContent();
+ if (child == content) {
+ // We are the base.
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ nsGkAtoms::base);
+ } else {
+ // Browse the list of scripts to find us and determine our type.
+ bool postscript = true;
+ bool subscript = true;
+ for (child = child->GetNextSibling(); child;
+ child = child->GetNextSibling()) {
+ if (!child->IsMathMLElement())
+ continue;
+ if (child->IsMathMLElement(nsGkAtoms::mprescripts_)) {
+ postscript = false;
+ subscript = true;
+ continue;
+ }
+ if (child == content) {
+ if (postscript) {
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ subscript ?
+ nsGkAtoms::subscript :
+ nsGkAtoms::superscript);
+ } else {
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::xmlroles,
+ subscript ?
+ nsGkAtoms::presubscript :
+ nsGkAtoms::presuperscript);
+ }
+ break;
+ }
+ subscript = !subscript;
+ }
+ }
+ }
+ } break;
+ default:
+ break;
+ }
+ }
+}
+
+already_AddRefed<nsIPersistentProperties>
+HyperTextAccessible::NativeAttributes()
+{
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ AccessibleWrap::NativeAttributes();
+
+ // 'formatting' attribute is deprecated, 'display' attribute should be
+ // instead.
+ nsIFrame *frame = GetFrame();
+ if (frame && frame->GetType() == nsGkAtoms::blockFrame) {
+ nsAutoString unused;
+ attributes->SetStringProperty(NS_LITERAL_CSTRING("formatting"),
+ NS_LITERAL_STRING("block"), unused);
+ }
+
+ if (FocusMgr()->IsFocused(this)) {
+ int32_t lineNumber = CaretLineNumber();
+ if (lineNumber >= 1) {
+ nsAutoString strLineNumber;
+ strLineNumber.AppendInt(lineNumber);
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::lineNumber, strLineNumber);
+ }
+ }
+
+ if (HasOwnContent()) {
+ GetAccService()->MarkupAttributes(mContent, attributes);
+ if (mContent->IsMathMLElement())
+ SetMathMLXMLRoles(attributes);
+ }
+
+ return attributes.forget();
+}
+
+nsIAtom*
+HyperTextAccessible::LandmarkRole() const
+{
+ if (!HasOwnContent())
+ return nullptr;
+
+ // For the html landmark elements we expose them like we do ARIA landmarks to
+ // make AT navigation schemes "just work".
+ if (mContent->IsHTMLElement(nsGkAtoms::nav)) {
+ return nsGkAtoms::navigation;
+ }
+
+ if (mContent->IsAnyOfHTMLElements(nsGkAtoms::header,
+ nsGkAtoms::footer)) {
+ // Only map header and footer if they are not descendants of an article
+ // or section tag.
+ nsIContent* parent = mContent->GetParent();
+ while (parent) {
+ if (parent->IsAnyOfHTMLElements(nsGkAtoms::article, nsGkAtoms::section)) {
+ break;
+ }
+ parent = parent->GetParent();
+ }
+
+ // No article or section elements found.
+ if (!parent) {
+ if (mContent->IsHTMLElement(nsGkAtoms::header)) {
+ return nsGkAtoms::banner;
+ }
+
+ if (mContent->IsHTMLElement(nsGkAtoms::footer)) {
+ return nsGkAtoms::contentinfo;
+ }
+ }
+ return nullptr;
+ }
+
+ if (mContent->IsHTMLElement(nsGkAtoms::aside)) {
+ return nsGkAtoms::complementary;
+ }
+
+ if (mContent->IsHTMLElement(nsGkAtoms::main)) {
+ return nsGkAtoms::main;
+ }
+
+ return nullptr;
+}
+
+int32_t
+HyperTextAccessible::OffsetAtPoint(int32_t aX, int32_t aY, uint32_t aCoordType)
+{
+ nsIFrame* hyperFrame = GetFrame();
+ if (!hyperFrame)
+ return -1;
+
+ nsIntPoint coords = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordType,
+ this);
+
+ nsPresContext* presContext = mDoc->PresContext();
+ nsPoint coordsInAppUnits =
+ ToAppUnits(coords, presContext->AppUnitsPerDevPixel());
+
+ nsRect frameScreenRect = hyperFrame->GetScreenRectInAppUnits();
+ if (!frameScreenRect.Contains(coordsInAppUnits.x, coordsInAppUnits.y))
+ return -1; // Not found
+
+ nsPoint pointInHyperText(coordsInAppUnits.x - frameScreenRect.x,
+ coordsInAppUnits.y - frameScreenRect.y);
+
+ // Go through the frames to check if each one has the point.
+ // When one does, add up the character offsets until we have a match
+
+ // We have an point in an accessible child of this, now we need to add up the
+ // offsets before it to what we already have
+ int32_t offset = 0;
+ uint32_t childCount = ChildCount();
+ for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
+ Accessible* childAcc = mChildren[childIdx];
+
+ nsIFrame *primaryFrame = childAcc->GetFrame();
+ NS_ENSURE_TRUE(primaryFrame, -1);
+
+ nsIFrame *frame = primaryFrame;
+ while (frame) {
+ nsIContent *content = frame->GetContent();
+ NS_ENSURE_TRUE(content, -1);
+ nsPoint pointInFrame = pointInHyperText - frame->GetOffsetTo(hyperFrame);
+ nsSize frameSize = frame->GetSize();
+ if (pointInFrame.x < frameSize.width && pointInFrame.y < frameSize.height) {
+ // Finished
+ if (frame->GetType() == nsGkAtoms::textFrame) {
+ nsIFrame::ContentOffsets contentOffsets =
+ frame->GetContentOffsetsFromPointExternal(pointInFrame, nsIFrame::IGNORE_SELECTION_STYLE);
+ if (contentOffsets.IsNull() || contentOffsets.content != content) {
+ return -1; // Not found
+ }
+ uint32_t addToOffset;
+ nsresult rv = ContentToRenderedOffset(primaryFrame,
+ contentOffsets.offset,
+ &addToOffset);
+ NS_ENSURE_SUCCESS(rv, -1);
+ offset += addToOffset;
+ }
+ return offset;
+ }
+ frame = frame->GetNextContinuation();
+ }
+
+ offset += nsAccUtils::TextLength(childAcc);
+ }
+
+ return -1; // Not found
+}
+
+nsIntRect
+HyperTextAccessible::TextBounds(int32_t aStartOffset, int32_t aEndOffset,
+ uint32_t aCoordType)
+{
+ index_t startOffset = ConvertMagicOffset(aStartOffset);
+ index_t endOffset = ConvertMagicOffset(aEndOffset);
+ if (!startOffset.IsValid() || !endOffset.IsValid() ||
+ startOffset > endOffset || endOffset > CharacterCount()) {
+ NS_ERROR("Wrong in offset");
+ return nsIntRect();
+ }
+
+
+ int32_t childIdx = GetChildIndexAtOffset(startOffset);
+ if (childIdx == -1)
+ return nsIntRect();
+
+ nsIntRect bounds;
+ int32_t prevOffset = GetChildOffset(childIdx);
+ int32_t offset1 = startOffset - prevOffset;
+
+ while (childIdx < static_cast<int32_t>(ChildCount())) {
+ nsIFrame* frame = GetChildAt(childIdx++)->GetFrame();
+ if (!frame) {
+ NS_NOTREACHED("No frame for a child!");
+ continue;
+ }
+
+ int32_t nextOffset = GetChildOffset(childIdx);
+ if (nextOffset >= static_cast<int32_t>(endOffset)) {
+ bounds.UnionRect(bounds, GetBoundsInFrame(frame, offset1,
+ endOffset - prevOffset));
+ break;
+ }
+
+ bounds.UnionRect(bounds, GetBoundsInFrame(frame, offset1,
+ nextOffset - prevOffset));
+
+ prevOffset = nextOffset;
+ offset1 = 0;
+ }
+
+ nsAccUtils::ConvertScreenCoordsTo(&bounds.x, &bounds.y, aCoordType, this);
+ return bounds;
+}
+
+already_AddRefed<nsIEditor>
+HyperTextAccessible::GetEditor() const
+{
+ if (!mContent->HasFlag(NODE_IS_EDITABLE)) {
+ // If we're inside an editable container, then return that container's editor
+ Accessible* ancestor = Parent();
+ while (ancestor) {
+ HyperTextAccessible* hyperText = ancestor->AsHyperText();
+ if (hyperText) {
+ // Recursion will stop at container doc because it has its own impl
+ // of GetEditor()
+ return hyperText->GetEditor();
+ }
+
+ ancestor = ancestor->Parent();
+ }
+
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mContent);
+ nsCOMPtr<nsIEditingSession> editingSession;
+ docShell->GetEditingSession(getter_AddRefs(editingSession));
+ if (!editingSession)
+ return nullptr; // No editing session interface
+
+ nsCOMPtr<nsIEditor> editor;
+ nsIDocument* docNode = mDoc->DocumentNode();
+ editingSession->GetEditorForWindow(docNode->GetWindow(),
+ getter_AddRefs(editor));
+ return editor.forget();
+}
+
+/**
+ * =================== Caret & Selection ======================
+ */
+
+nsresult
+HyperTextAccessible::SetSelectionRange(int32_t aStartPos, int32_t aEndPos)
+{
+ // Before setting the selection range, we need to ensure that the editor
+ // is initialized. (See bug 804927.)
+ // Otherwise, it's possible that lazy editor initialization will override
+ // the selection we set here and leave the caret at the end of the text.
+ // By calling GetEditor here, we ensure that editor initialization is
+ // completed before we set the selection.
+ nsCOMPtr<nsIEditor> editor = GetEditor();
+
+ bool isFocusable = InteractiveState() & states::FOCUSABLE;
+
+ // If accessible is focusable then focus it before setting the selection to
+ // neglect control's selection changes on focus if any (for example, inputs
+ // that do select all on focus).
+ // some input controls
+ if (isFocusable)
+ TakeFocus();
+
+ dom::Selection* domSel = DOMSelection();
+ NS_ENSURE_STATE(domSel);
+
+ // Set up the selection.
+ for (int32_t idx = domSel->RangeCount() - 1; idx > 0; idx--)
+ domSel->RemoveRange(domSel->GetRangeAt(idx));
+ SetSelectionBoundsAt(0, aStartPos, aEndPos);
+
+ // When selection is done, move the focus to the selection if accessible is
+ // not focusable. That happens when selection is set within hypertext
+ // accessible.
+ if (isFocusable)
+ return NS_OK;
+
+ nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager();
+ if (DOMFocusManager) {
+ NS_ENSURE_TRUE(mDoc, NS_ERROR_FAILURE);
+ nsIDocument* docNode = mDoc->DocumentNode();
+ NS_ENSURE_TRUE(docNode, NS_ERROR_FAILURE);
+ nsCOMPtr<nsPIDOMWindowOuter> window = docNode->GetWindow();
+ nsCOMPtr<nsIDOMElement> result;
+ DOMFocusManager->MoveFocus(window, nullptr, nsIFocusManager::MOVEFOCUS_CARET,
+ nsIFocusManager::FLAG_BYMOVEFOCUS, getter_AddRefs(result));
+ }
+
+ return NS_OK;
+}
+
+int32_t
+HyperTextAccessible::CaretOffset() const
+{
+ // Not focused focusable accessible except document accessible doesn't have
+ // a caret.
+ if (!IsDoc() && !FocusMgr()->IsFocused(this) &&
+ (InteractiveState() & states::FOCUSABLE)) {
+ return -1;
+ }
+
+ // Check cached value.
+ int32_t caretOffset = -1;
+ HyperTextAccessible* text = SelectionMgr()->AccessibleWithCaret(&caretOffset);
+
+ // Use cached value if it corresponds to this accessible.
+ if (caretOffset != -1) {
+ if (text == this)
+ return caretOffset;
+
+ nsINode* textNode = text->GetNode();
+ // Ignore offset if cached accessible isn't a text leaf.
+ if (nsCoreUtils::IsAncestorOf(GetNode(), textNode))
+ return TransformOffset(text,
+ textNode->IsNodeOfType(nsINode::eTEXT) ? caretOffset : 0, false);
+ }
+
+ // No caret if the focused node is not inside this DOM node and this DOM node
+ // is not inside of focused node.
+ FocusManager::FocusDisposition focusDisp =
+ FocusMgr()->IsInOrContainsFocus(this);
+ if (focusDisp == FocusManager::eNone)
+ return -1;
+
+ // Turn the focus node and offset of the selection into caret hypretext
+ // offset.
+ dom::Selection* domSel = DOMSelection();
+ NS_ENSURE_TRUE(domSel, -1);
+
+ nsINode* focusNode = domSel->GetFocusNode();
+ uint32_t focusOffset = domSel->FocusOffset();
+
+ // No caret if this DOM node is inside of focused node but the selection's
+ // focus point is not inside of this DOM node.
+ if (focusDisp == FocusManager::eContainedByFocus) {
+ nsINode* resultNode =
+ nsCoreUtils::GetDOMNodeFromDOMPoint(focusNode, focusOffset);
+
+ nsINode* thisNode = GetNode();
+ if (resultNode != thisNode &&
+ !nsCoreUtils::IsAncestorOf(thisNode, resultNode))
+ return -1;
+ }
+
+ return DOMPointToOffset(focusNode, focusOffset);
+}
+
+int32_t
+HyperTextAccessible::CaretLineNumber()
+{
+ // Provide the line number for the caret, relative to the
+ // currently focused node. Use a 1-based index
+ RefPtr<nsFrameSelection> frameSelection = FrameSelection();
+ if (!frameSelection)
+ return -1;
+
+ dom::Selection* domSel = frameSelection->GetSelection(SelectionType::eNormal);
+ if (!domSel)
+ return - 1;
+
+ nsINode* caretNode = domSel->GetFocusNode();
+ if (!caretNode || !caretNode->IsContent())
+ return -1;
+
+ nsIContent* caretContent = caretNode->AsContent();
+ if (!nsCoreUtils::IsAncestorOf(GetNode(), caretContent))
+ return -1;
+
+ int32_t returnOffsetUnused;
+ uint32_t caretOffset = domSel->FocusOffset();
+ CaretAssociationHint hint = frameSelection->GetHint();
+ nsIFrame *caretFrame = frameSelection->GetFrameForNodeOffset(caretContent, caretOffset,
+ hint, &returnOffsetUnused);
+ NS_ENSURE_TRUE(caretFrame, -1);
+
+ int32_t lineNumber = 1;
+ nsAutoLineIterator lineIterForCaret;
+ nsIContent *hyperTextContent = IsContent() ? mContent.get() : nullptr;
+ while (caretFrame) {
+ if (hyperTextContent == caretFrame->GetContent()) {
+ return lineNumber; // Must be in a single line hyper text, there is no line iterator
+ }
+ nsContainerFrame *parentFrame = caretFrame->GetParent();
+ if (!parentFrame)
+ break;
+
+ // Add lines for the sibling frames before the caret
+ nsIFrame *sibling = parentFrame->PrincipalChildList().FirstChild();
+ while (sibling && sibling != caretFrame) {
+ nsAutoLineIterator lineIterForSibling = sibling->GetLineIterator();
+ if (lineIterForSibling) {
+ // For the frames before that grab all the lines
+ int32_t addLines = lineIterForSibling->GetNumLines();
+ lineNumber += addLines;
+ }
+ sibling = sibling->GetNextSibling();
+ }
+
+ // Get the line number relative to the container with lines
+ if (!lineIterForCaret) { // Add the caret line just once
+ lineIterForCaret = parentFrame->GetLineIterator();
+ if (lineIterForCaret) {
+ // Ancestor of caret
+ int32_t addLines = lineIterForCaret->FindLineContaining(caretFrame);
+ lineNumber += addLines;
+ }
+ }
+
+ caretFrame = parentFrame;
+ }
+
+ NS_NOTREACHED("DOM ancestry had this hypertext but frame ancestry didn't");
+ return lineNumber;
+}
+
+LayoutDeviceIntRect
+HyperTextAccessible::GetCaretRect(nsIWidget** aWidget)
+{
+ *aWidget = nullptr;
+
+ RefPtr<nsCaret> caret = mDoc->PresShell()->GetCaret();
+ NS_ENSURE_TRUE(caret, LayoutDeviceIntRect());
+
+ bool isVisible = caret->IsVisible();
+ if (!isVisible)
+ return LayoutDeviceIntRect();
+
+ nsRect rect;
+ nsIFrame* frame = caret->GetGeometry(&rect);
+ if (!frame || rect.IsEmpty())
+ return LayoutDeviceIntRect();
+
+ nsPoint offset;
+ // Offset from widget origin to the frame origin, which includes chrome
+ // on the widget.
+ *aWidget = frame->GetNearestWidget(offset);
+ NS_ENSURE_TRUE(*aWidget, LayoutDeviceIntRect());
+ rect.MoveBy(offset);
+
+ LayoutDeviceIntRect caretRect = LayoutDeviceIntRect::FromUnknownRect(
+ rect.ToOutsidePixels(frame->PresContext()->AppUnitsPerDevPixel()));
+ // ((content screen origin) - (content offset in the widget)) = widget origin on the screen
+ caretRect.MoveBy((*aWidget)->WidgetToScreenOffset() - (*aWidget)->GetClientOffset());
+
+ // Correct for character size, so that caret always matches the size of
+ // the character. This is important for font size transitions, and is
+ // necessary because the Gecko caret uses the previous character's size as
+ // the user moves forward in the text by character.
+ nsIntRect charRect = CharBounds(CaretOffset(),
+ nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE);
+ if (!charRect.IsEmpty()) {
+ caretRect.height -= charRect.y - caretRect.y;
+ caretRect.y = charRect.y;
+ }
+ return caretRect;
+}
+
+void
+HyperTextAccessible::GetSelectionDOMRanges(SelectionType aSelectionType,
+ nsTArray<nsRange*>* aRanges)
+{
+ // Ignore selection if it is not visible.
+ RefPtr<nsFrameSelection> frameSelection = FrameSelection();
+ if (!frameSelection ||
+ frameSelection->GetDisplaySelection() <= nsISelectionController::SELECTION_HIDDEN)
+ return;
+
+ dom::Selection* domSel = frameSelection->GetSelection(aSelectionType);
+ if (!domSel)
+ return;
+
+ nsCOMPtr<nsINode> startNode = GetNode();
+
+ nsCOMPtr<nsIEditor> editor = GetEditor();
+ if (editor) {
+ nsCOMPtr<nsIDOMElement> editorRoot;
+ editor->GetRootElement(getter_AddRefs(editorRoot));
+ startNode = do_QueryInterface(editorRoot);
+ }
+
+ if (!startNode)
+ return;
+
+ uint32_t childCount = startNode->GetChildCount();
+ nsresult rv = domSel->
+ GetRangesForIntervalArray(startNode, 0, startNode, childCount, true, aRanges);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // Remove collapsed ranges
+ uint32_t numRanges = aRanges->Length();
+ for (uint32_t idx = 0; idx < numRanges; idx ++) {
+ if ((*aRanges)[idx]->Collapsed()) {
+ aRanges->RemoveElementAt(idx);
+ --numRanges;
+ --idx;
+ }
+ }
+}
+
+int32_t
+HyperTextAccessible::SelectionCount()
+{
+ nsTArray<nsRange*> ranges;
+ GetSelectionDOMRanges(SelectionType::eNormal, &ranges);
+ return ranges.Length();
+}
+
+bool
+HyperTextAccessible::SelectionBoundsAt(int32_t aSelectionNum,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset)
+{
+ *aStartOffset = *aEndOffset = 0;
+
+ nsTArray<nsRange*> ranges;
+ GetSelectionDOMRanges(SelectionType::eNormal, &ranges);
+
+ uint32_t rangeCount = ranges.Length();
+ if (aSelectionNum < 0 || aSelectionNum >= static_cast<int32_t>(rangeCount))
+ return false;
+
+ nsRange* range = ranges[aSelectionNum];
+
+ // Get start and end points.
+ nsINode* startNode = range->GetStartParent();
+ nsINode* endNode = range->GetEndParent();
+ int32_t startOffset = range->StartOffset(), endOffset = range->EndOffset();
+
+ // Make sure start is before end, by swapping DOM points. This occurs when
+ // the user selects backwards in the text.
+ int32_t rangeCompare = nsContentUtils::ComparePoints(endNode, endOffset,
+ startNode, startOffset);
+ if (rangeCompare < 0) {
+ nsINode* tempNode = startNode;
+ startNode = endNode;
+ endNode = tempNode;
+ int32_t tempOffset = startOffset;
+ startOffset = endOffset;
+ endOffset = tempOffset;
+ }
+
+ if (!nsContentUtils::ContentIsDescendantOf(startNode, mContent))
+ *aStartOffset = 0;
+ else
+ *aStartOffset = DOMPointToOffset(startNode, startOffset);
+
+ if (!nsContentUtils::ContentIsDescendantOf(endNode, mContent))
+ *aEndOffset = CharacterCount();
+ else
+ *aEndOffset = DOMPointToOffset(endNode, endOffset, true);
+ return true;
+}
+
+bool
+HyperTextAccessible::SetSelectionBoundsAt(int32_t aSelectionNum,
+ int32_t aStartOffset,
+ int32_t aEndOffset)
+{
+ index_t startOffset = ConvertMagicOffset(aStartOffset);
+ index_t endOffset = ConvertMagicOffset(aEndOffset);
+ if (!startOffset.IsValid() || !endOffset.IsValid() ||
+ startOffset > endOffset || endOffset > CharacterCount()) {
+ NS_ERROR("Wrong in offset");
+ return false;
+ }
+
+ dom::Selection* domSel = DOMSelection();
+ if (!domSel)
+ return false;
+
+ RefPtr<nsRange> range;
+ uint32_t rangeCount = domSel->RangeCount();
+ if (aSelectionNum == static_cast<int32_t>(rangeCount))
+ range = new nsRange(mContent);
+ else
+ range = domSel->GetRangeAt(aSelectionNum);
+
+ if (!range)
+ return false;
+
+ if (!OffsetsToDOMRange(startOffset, endOffset, range))
+ return false;
+
+ // If new range was created then add it, otherwise notify selection listeners
+ // that existing selection range was changed.
+ if (aSelectionNum == static_cast<int32_t>(rangeCount))
+ return NS_SUCCEEDED(domSel->AddRange(range));
+
+ domSel->RemoveRange(range);
+ return NS_SUCCEEDED(domSel->AddRange(range));
+}
+
+bool
+HyperTextAccessible::RemoveFromSelection(int32_t aSelectionNum)
+{
+ dom::Selection* domSel = DOMSelection();
+ if (!domSel)
+ return false;
+
+ if (aSelectionNum < 0 || aSelectionNum >= static_cast<int32_t>(domSel->RangeCount()))
+ return false;
+
+ domSel->RemoveRange(domSel->GetRangeAt(aSelectionNum));
+ return true;
+}
+
+void
+HyperTextAccessible::ScrollSubstringTo(int32_t aStartOffset, int32_t aEndOffset,
+ uint32_t aScrollType)
+{
+ RefPtr<nsRange> range = new nsRange(mContent);
+ if (OffsetsToDOMRange(aStartOffset, aEndOffset, range))
+ nsCoreUtils::ScrollSubstringTo(GetFrame(), range, aScrollType);
+}
+
+void
+HyperTextAccessible::ScrollSubstringToPoint(int32_t aStartOffset,
+ int32_t aEndOffset,
+ uint32_t aCoordinateType,
+ int32_t aX, int32_t aY)
+{
+ nsIFrame *frame = GetFrame();
+ if (!frame)
+ return;
+
+ nsIntPoint coords = nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType,
+ this);
+
+ RefPtr<nsRange> range = new nsRange(mContent);
+ if (!OffsetsToDOMRange(aStartOffset, aEndOffset, range))
+ return;
+
+ nsPresContext* presContext = frame->PresContext();
+ nsPoint coordsInAppUnits =
+ ToAppUnits(coords, presContext->AppUnitsPerDevPixel());
+
+ bool initialScrolled = false;
+ nsIFrame *parentFrame = frame;
+ while ((parentFrame = parentFrame->GetParent())) {
+ nsIScrollableFrame *scrollableFrame = do_QueryFrame(parentFrame);
+ if (scrollableFrame) {
+ if (!initialScrolled) {
+ // Scroll substring to the given point. Turn the point into percents
+ // relative scrollable area to use nsCoreUtils::ScrollSubstringTo.
+ nsRect frameRect = parentFrame->GetScreenRectInAppUnits();
+ nscoord offsetPointX = coordsInAppUnits.x - frameRect.x;
+ nscoord offsetPointY = coordsInAppUnits.y - frameRect.y;
+
+ nsSize size(parentFrame->GetSize());
+
+ // avoid divide by zero
+ size.width = size.width ? size.width : 1;
+ size.height = size.height ? size.height : 1;
+
+ int16_t hPercent = offsetPointX * 100 / size.width;
+ int16_t vPercent = offsetPointY * 100 / size.height;
+
+ nsresult rv = nsCoreUtils::ScrollSubstringTo(frame, range,
+ nsIPresShell::ScrollAxis(vPercent),
+ nsIPresShell::ScrollAxis(hPercent));
+ if (NS_FAILED(rv))
+ return;
+
+ initialScrolled = true;
+ } else {
+ // Substring was scrolled to the given point already inside its closest
+ // scrollable area. If there are nested scrollable areas then make
+ // sure we scroll lower areas to the given point inside currently
+ // traversed scrollable area.
+ nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords);
+ }
+ }
+ frame = parentFrame;
+ }
+}
+
+void
+HyperTextAccessible::EnclosingRange(a11y::TextRange& aRange) const
+{
+ if (IsTextField()) {
+ aRange.Set(mDoc, const_cast<HyperTextAccessible*>(this), 0,
+ const_cast<HyperTextAccessible*>(this), CharacterCount());
+ } else {
+ aRange.Set(mDoc, mDoc, 0, mDoc, mDoc->CharacterCount());
+ }
+}
+
+void
+HyperTextAccessible::SelectionRanges(nsTArray<a11y::TextRange>* aRanges) const
+{
+ MOZ_ASSERT(aRanges->Length() == 0, "TextRange array supposed to be empty");
+
+ dom::Selection* sel = DOMSelection();
+ if (!sel)
+ return;
+
+ aRanges->SetCapacity(sel->RangeCount());
+
+ for (uint32_t idx = 0; idx < sel->RangeCount(); idx++) {
+ nsRange* DOMRange = sel->GetRangeAt(idx);
+ HyperTextAccessible* startParent =
+ nsAccUtils::GetTextContainer(DOMRange->GetStartParent());
+ HyperTextAccessible* endParent =
+ nsAccUtils::GetTextContainer(DOMRange->GetEndParent());
+ if (!startParent || !endParent)
+ continue;
+
+ int32_t startOffset =
+ startParent->DOMPointToOffset(DOMRange->GetStartParent(),
+ DOMRange->StartOffset(), false);
+ int32_t endOffset =
+ endParent->DOMPointToOffset(DOMRange->GetEndParent(),
+ DOMRange->EndOffset(), true);
+
+ TextRange tr(IsTextField() ? const_cast<HyperTextAccessible*>(this) : mDoc,
+ startParent, startOffset, endParent, endOffset);
+ *(aRanges->AppendElement()) = Move(tr);
+ }
+}
+
+void
+HyperTextAccessible::VisibleRanges(nsTArray<a11y::TextRange>* aRanges) const
+{
+}
+
+void
+HyperTextAccessible::RangeByChild(Accessible* aChild,
+ a11y::TextRange& aRange) const
+{
+ HyperTextAccessible* ht = aChild->AsHyperText();
+ if (ht) {
+ aRange.Set(mDoc, ht, 0, ht, ht->CharacterCount());
+ return;
+ }
+
+ Accessible* child = aChild;
+ Accessible* parent = nullptr;
+ while ((parent = child->Parent()) && !(ht = parent->AsHyperText()))
+ child = parent;
+
+ // If no text then return collapsed text range, otherwise return a range
+ // containing the text enclosed by the given child.
+ if (ht) {
+ int32_t childIdx = child->IndexInParent();
+ int32_t startOffset = ht->GetChildOffset(childIdx);
+ int32_t endOffset = child->IsTextLeaf() ?
+ ht->GetChildOffset(childIdx + 1) : startOffset;
+ aRange.Set(mDoc, ht, startOffset, ht, endOffset);
+ }
+}
+
+void
+HyperTextAccessible::RangeAtPoint(int32_t aX, int32_t aY,
+ a11y::TextRange& aRange) const
+{
+ Accessible* child = mDoc->ChildAtPoint(aX, aY, eDeepestChild);
+ if (!child)
+ return;
+
+ Accessible* parent = nullptr;
+ while ((parent = child->Parent()) && !parent->IsHyperText())
+ child = parent;
+
+ // Return collapsed text range for the point.
+ if (parent) {
+ HyperTextAccessible* ht = parent->AsHyperText();
+ int32_t offset = ht->GetChildOffset(child);
+ aRange.Set(mDoc, ht, offset, ht, offset);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible public
+
+// Accessible protected
+ENameValueFlag
+HyperTextAccessible::NativeName(nsString& aName)
+{
+ // Check @alt attribute for invalid img elements.
+ bool hasImgAlt = false;
+ if (mContent->IsHTMLElement(nsGkAtoms::img)) {
+ hasImgAlt = mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::alt, aName);
+ if (!aName.IsEmpty())
+ return eNameOK;
+ }
+
+ ENameValueFlag nameFlag = AccessibleWrap::NativeName(aName);
+ if (!aName.IsEmpty())
+ return nameFlag;
+
+ // Get name from title attribute for HTML abbr and acronym elements making it
+ // a valid name from markup. Otherwise their name isn't picked up by recursive
+ // name computation algorithm. See NS_OK_NAME_FROM_TOOLTIP.
+ if (IsAbbreviation() &&
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::title, aName))
+ aName.CompressWhitespace();
+
+ return hasImgAlt ? eNoNameOnPurpose : eNameOK;
+}
+
+void
+HyperTextAccessible::Shutdown()
+{
+ mOffsets.Clear();
+ AccessibleWrap::Shutdown();
+}
+
+bool
+HyperTextAccessible::RemoveChild(Accessible* aAccessible)
+{
+ int32_t childIndex = aAccessible->IndexInParent();
+ int32_t count = mOffsets.Length() - childIndex;
+ if (count > 0)
+ mOffsets.RemoveElementsAt(childIndex, count);
+
+ return AccessibleWrap::RemoveChild(aAccessible);
+}
+
+bool
+HyperTextAccessible::InsertChildAt(uint32_t aIndex, Accessible* aChild)
+{
+ int32_t count = mOffsets.Length() - aIndex;
+ if (count > 0 ) {
+ mOffsets.RemoveElementsAt(aIndex, count);
+ }
+ return AccessibleWrap::InsertChildAt(aIndex, aChild);
+}
+
+Relation
+HyperTextAccessible::RelationByType(RelationType aType)
+{
+ Relation rel = Accessible::RelationByType(aType);
+
+ switch (aType) {
+ case RelationType::NODE_CHILD_OF:
+ if (HasOwnContent() && mContent->IsMathMLElement()) {
+ Accessible* parent = Parent();
+ if (parent) {
+ nsIContent* parentContent = parent->GetContent();
+ if (parentContent &&
+ parentContent->IsMathMLElement(nsGkAtoms::mroot_)) {
+ // Add a relation pointing to the parent <mroot>.
+ rel.AppendTarget(parent);
+ }
+ }
+ }
+ break;
+ case RelationType::NODE_PARENT_OF:
+ if (HasOwnContent() && mContent->IsMathMLElement(nsGkAtoms::mroot_)) {
+ Accessible* base = GetChildAt(0);
+ Accessible* index = GetChildAt(1);
+ if (base && index) {
+ // Append the <mroot> children in the order index, base.
+ rel.AppendTarget(index);
+ rel.AppendTarget(base);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return rel;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HyperTextAccessible public static
+
+nsresult
+HyperTextAccessible::ContentToRenderedOffset(nsIFrame* aFrame, int32_t aContentOffset,
+ uint32_t* aRenderedOffset) const
+{
+ if (!aFrame) {
+ // Current frame not rendered -- this can happen if text is set on
+ // something with display: none
+ *aRenderedOffset = 0;
+ return NS_OK;
+ }
+
+ if (IsTextField()) {
+ *aRenderedOffset = aContentOffset;
+ return NS_OK;
+ }
+
+ NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame,
+ "Need text frame for offset conversion");
+ NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr,
+ "Call on primary frame only");
+
+ nsIFrame::RenderedText text = aFrame->GetRenderedText(aContentOffset,
+ aContentOffset + 1, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
+ nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
+ *aRenderedOffset = text.mOffsetWithinNodeRenderedText;
+
+ return NS_OK;
+}
+
+nsresult
+HyperTextAccessible::RenderedToContentOffset(nsIFrame* aFrame, uint32_t aRenderedOffset,
+ int32_t* aContentOffset) const
+{
+ if (IsTextField()) {
+ *aContentOffset = aRenderedOffset;
+ return NS_OK;
+ }
+
+ *aContentOffset = 0;
+ NS_ENSURE_TRUE(aFrame, NS_ERROR_FAILURE);
+
+ NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame,
+ "Need text frame for offset conversion");
+ NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr,
+ "Call on primary frame only");
+
+ nsIFrame::RenderedText text = aFrame->GetRenderedText(aRenderedOffset,
+ aRenderedOffset + 1, nsIFrame::TextOffsetType::OFFSETS_IN_RENDERED_TEXT,
+ nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
+ *aContentOffset = text.mOffsetWithinNodeText;
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HyperTextAccessible public
+
+int32_t
+HyperTextAccessible::GetChildOffset(uint32_t aChildIndex,
+ bool aInvalidateAfter) const
+{
+ if (aChildIndex == 0) {
+ if (aInvalidateAfter)
+ mOffsets.Clear();
+
+ return aChildIndex;
+ }
+
+ int32_t count = mOffsets.Length() - aChildIndex;
+ if (count > 0) {
+ if (aInvalidateAfter)
+ mOffsets.RemoveElementsAt(aChildIndex, count);
+
+ return mOffsets[aChildIndex - 1];
+ }
+
+ uint32_t lastOffset = mOffsets.IsEmpty() ?
+ 0 : mOffsets[mOffsets.Length() - 1];
+
+ while (mOffsets.Length() < aChildIndex) {
+ Accessible* child = mChildren[mOffsets.Length()];
+ lastOffset += nsAccUtils::TextLength(child);
+ mOffsets.AppendElement(lastOffset);
+ }
+
+ return mOffsets[aChildIndex - 1];
+}
+
+int32_t
+HyperTextAccessible::GetChildIndexAtOffset(uint32_t aOffset) const
+{
+ uint32_t lastOffset = 0;
+ const uint32_t offsetCount = mOffsets.Length();
+
+ if (offsetCount > 0) {
+ lastOffset = mOffsets[offsetCount - 1];
+ if (aOffset < lastOffset) {
+ size_t index;
+ if (BinarySearch(mOffsets, 0, offsetCount, aOffset, &index)) {
+ return (index < (offsetCount - 1)) ? index + 1 : index;
+ }
+
+ return (index == offsetCount) ? -1 : index;
+ }
+ }
+
+ uint32_t childCount = ChildCount();
+ while (mOffsets.Length() < childCount) {
+ Accessible* child = GetChildAt(mOffsets.Length());
+ lastOffset += nsAccUtils::TextLength(child);
+ mOffsets.AppendElement(lastOffset);
+ if (aOffset < lastOffset)
+ return mOffsets.Length() - 1;
+ }
+
+ if (aOffset == lastOffset)
+ return mOffsets.Length() - 1;
+
+ return -1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HyperTextAccessible protected
+
+nsresult
+HyperTextAccessible::GetDOMPointByFrameOffset(nsIFrame* aFrame, int32_t aOffset,
+ Accessible* aAccessible,
+ DOMPoint* aPoint)
+{
+ NS_ENSURE_ARG(aAccessible);
+
+ if (!aFrame) {
+ // If the given frame is null then set offset after the DOM node of the
+ // given accessible.
+ NS_ASSERTION(!aAccessible->IsDoc(),
+ "Shouldn't be called on document accessible!");
+
+ nsIContent* content = aAccessible->GetContent();
+ NS_ASSERTION(content, "Shouldn't operate on defunct accessible!");
+
+ nsIContent* parent = content->GetParent();
+
+ aPoint->idx = parent->IndexOf(content) + 1;
+ aPoint->node = parent;
+
+ } else if (aFrame->GetType() == nsGkAtoms::textFrame) {
+ nsIContent* content = aFrame->GetContent();
+ NS_ENSURE_STATE(content);
+
+ nsIFrame *primaryFrame = content->GetPrimaryFrame();
+ nsresult rv = RenderedToContentOffset(primaryFrame, aOffset, &(aPoint->idx));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aPoint->node = content;
+
+ } else {
+ nsIContent* content = aFrame->GetContent();
+ NS_ENSURE_STATE(content);
+
+ nsIContent* parent = content->GetParent();
+ NS_ENSURE_STATE(parent);
+
+ aPoint->idx = parent->IndexOf(content);
+ aPoint->node = parent;
+ }
+
+ return NS_OK;
+}
+
+// HyperTextAccessible
+void
+HyperTextAccessible::GetSpellTextAttr(nsINode* aNode,
+ int32_t aNodeOffset,
+ uint32_t* aStartOffset,
+ uint32_t* aEndOffset,
+ nsIPersistentProperties* aAttributes)
+{
+ RefPtr<nsFrameSelection> fs = FrameSelection();
+ if (!fs)
+ return;
+
+ dom::Selection* domSel = fs->GetSelection(SelectionType::eSpellCheck);
+ if (!domSel)
+ return;
+
+ int32_t rangeCount = domSel->RangeCount();
+ if (rangeCount <= 0)
+ return;
+
+ uint32_t startOffset = 0, endOffset = 0;
+ for (int32_t idx = 0; idx < rangeCount; idx++) {
+ nsRange* range = domSel->GetRangeAt(idx);
+ if (range->Collapsed())
+ continue;
+
+ // See if the point comes after the range in which case we must continue in
+ // case there is another range after this one.
+ nsINode* endNode = range->GetEndParent();
+ int32_t endNodeOffset = range->EndOffset();
+ if (nsContentUtils::ComparePoints(aNode, aNodeOffset,
+ endNode, endNodeOffset) >= 0)
+ continue;
+
+ // At this point our point is either in this range or before it but after
+ // the previous range. So we check to see if the range starts before the
+ // point in which case the point is in the missspelled range, otherwise it
+ // must be before the range and after the previous one if any.
+ nsINode* startNode = range->GetStartParent();
+ int32_t startNodeOffset = range->StartOffset();
+ if (nsContentUtils::ComparePoints(startNode, startNodeOffset, aNode,
+ aNodeOffset) <= 0) {
+ startOffset = DOMPointToOffset(startNode, startNodeOffset);
+
+ endOffset = DOMPointToOffset(endNode, endNodeOffset);
+
+ if (startOffset > *aStartOffset)
+ *aStartOffset = startOffset;
+
+ if (endOffset < *aEndOffset)
+ *aEndOffset = endOffset;
+
+ if (aAttributes) {
+ nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid,
+ NS_LITERAL_STRING("spelling"));
+ }
+
+ return;
+ }
+
+ // This range came after the point.
+ endOffset = DOMPointToOffset(startNode, startNodeOffset);
+
+ if (idx > 0) {
+ nsRange* prevRange = domSel->GetRangeAt(idx - 1);
+ startOffset = DOMPointToOffset(prevRange->GetEndParent(),
+ prevRange->EndOffset());
+ }
+
+ if (startOffset > *aStartOffset)
+ *aStartOffset = startOffset;
+
+ if (endOffset < *aEndOffset)
+ *aEndOffset = endOffset;
+
+ return;
+ }
+
+ // We never found a range that ended after the point, therefore we know that
+ // the point is not in a range, that we do not need to compute an end offset,
+ // and that we should use the end offset of the last range to compute the
+ // start offset of the text attribute range.
+ nsRange* prevRange = domSel->GetRangeAt(rangeCount - 1);
+ startOffset = DOMPointToOffset(prevRange->GetEndParent(),
+ prevRange->EndOffset());
+
+ if (startOffset > *aStartOffset)
+ *aStartOffset = startOffset;
+}
+
+bool
+HyperTextAccessible::IsTextRole()
+{
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (roleMapEntry &&
+ (roleMapEntry->role == roles::GRAPHIC ||
+ roleMapEntry->role == roles::IMAGE_MAP ||
+ roleMapEntry->role == roles::SLIDER ||
+ roleMapEntry->role == roles::PROGRESSBAR ||
+ roleMapEntry->role == roles::SEPARATOR))
+ return false;
+
+ return true;
+}
diff --git a/accessible/generic/HyperTextAccessible.h b/accessible/generic/HyperTextAccessible.h
new file mode 100644
index 000000000..78e73f042
--- /dev/null
+++ b/accessible/generic/HyperTextAccessible.h
@@ -0,0 +1,592 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_HyperTextAccessible_h__
+#define mozilla_a11y_HyperTextAccessible_h__
+
+#include "AccessibleWrap.h"
+#include "nsIAccessibleText.h"
+#include "nsIAccessibleTypes.h"
+#include "nsDirection.h"
+#include "WordMovementType.h"
+#include "nsIFrame.h"
+
+#include "nsISelectionController.h"
+
+class nsFrameSelection;
+class nsRange;
+class nsIWidget;
+
+namespace mozilla {
+
+namespace dom {
+class Selection;
+}
+
+namespace a11y {
+
+class TextRange;
+
+struct DOMPoint {
+ DOMPoint() : node(nullptr), idx(0) { }
+ DOMPoint(nsINode* aNode, int32_t aIdx) : node(aNode), idx(aIdx) { }
+
+ nsINode* node;
+ int32_t idx;
+};
+
+// This character marks where in the text returned via Text interface,
+// that embedded object characters exist
+const char16_t kEmbeddedObjectChar = 0xfffc;
+const char16_t kImaginaryEmbeddedObjectChar = ' ';
+const char16_t kForcedNewLineChar = '\n';
+
+/**
+ * Special Accessible that knows how contain both text and embedded objects
+ */
+class HyperTextAccessible : public AccessibleWrap
+{
+public:
+ HyperTextAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual nsIAtom* LandmarkRole() const override;
+ virtual int32_t GetLevelInternal() override;
+ virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override;
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+
+ virtual void Shutdown() override;
+ virtual bool RemoveChild(Accessible* aAccessible) override;
+ virtual bool InsertChildAt(uint32_t aIndex, Accessible* aChild) override;
+ virtual Relation RelationByType(RelationType aType) override;
+
+ // HyperTextAccessible (static helper method)
+
+ // Convert content offset to rendered text offset
+ nsresult ContentToRenderedOffset(nsIFrame *aFrame, int32_t aContentOffset,
+ uint32_t *aRenderedOffset) const;
+
+ // Convert rendered text offset to content offset
+ nsresult RenderedToContentOffset(nsIFrame *aFrame, uint32_t aRenderedOffset,
+ int32_t *aContentOffset) const;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // HyperLinkAccessible
+
+ /**
+ * Return link count within this hypertext accessible.
+ */
+ uint32_t LinkCount()
+ { return EmbeddedChildCount(); }
+
+ /**
+ * Return link accessible at the given index.
+ */
+ Accessible* LinkAt(uint32_t aIndex)
+ {
+ return GetEmbeddedChildAt(aIndex);
+ }
+
+ /**
+ * Return index for the given link accessible.
+ */
+ int32_t LinkIndexOf(Accessible* aLink)
+ {
+ return GetIndexOfEmbeddedChild(aLink);
+ }
+
+ /**
+ * Return link accessible at the given text offset.
+ */
+ int32_t LinkIndexAtOffset(uint32_t aOffset)
+ {
+ Accessible* child = GetChildAtOffset(aOffset);
+ return child ? LinkIndexOf(child) : -1;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // HyperTextAccessible: DOM point to text offset conversions.
+
+ /**
+ * Turn a DOM point (node and offset) into a character offset of this
+ * hypertext. Will look for closest match when the DOM node does not have
+ * an accessible object associated with it. Will return an offset for the end
+ * of the string if the node is not found.
+ *
+ * @param aNode [in] the node to look for
+ * @param aNodeOffset [in] the offset to look for
+ * if -1 just look directly for the node
+ * if >=0 and aNode is text, this represents a char offset
+ * if >=0 and aNode is not text, this represents a child node offset
+ * @param aIsEndOffset [in] if true, then then this offset is not inclusive. The character
+ * indicated by the offset returned is at [offset - 1]. This means
+ * if the passed-in offset is really in a descendant, then the offset returned
+ * will come just after the relevant embedded object characer.
+ * If false, then the offset is inclusive. The character indicated
+ * by the offset returned is at [offset]. If the passed-in offset in inside a
+ * descendant, then the returned offset will be on the relevant embedded object char.
+ */
+ uint32_t DOMPointToOffset(nsINode* aNode, int32_t aNodeOffset,
+ bool aIsEndOffset = false) const;
+
+ /**
+ * Transform the given a11y point into the offset relative this hypertext.
+ */
+ uint32_t TransformOffset(Accessible* aDescendant, uint32_t aOffset,
+ bool aIsEndOffset) const;
+
+ /**
+ * Convert start and end hypertext offsets into DOM range. Note that if
+ * aStartOffset and/or aEndOffset is in generated content such as ::before or
+ * ::after, the result range excludes the generated content. See also
+ * ClosestNotGeneratedDOMPoint() for more information.
+ *
+ * @param aStartOffset [in] the given start hypertext offset
+ * @param aEndOffset [in] the given end hypertext offset
+ * @param aRange [in, out] the range whose bounds to set
+ * @return true if conversion was successful
+ */
+ bool OffsetsToDOMRange(int32_t aStartOffset, int32_t aEndOffset,
+ nsRange* aRange);
+
+ /**
+ * Convert the given offset into DOM point.
+ *
+ * If offset is at text leaf then DOM point is (text node, offsetInTextNode),
+ * if before embedded object then (parent node, indexInParent), if after then
+ * (parent node, indexInParent + 1).
+ */
+ DOMPoint OffsetToDOMPoint(int32_t aOffset);
+
+ /**
+ * Return true if the used ARIA role (if any) allows the hypertext accessible
+ * to expose text interfaces.
+ */
+ bool IsTextRole();
+
+ //////////////////////////////////////////////////////////////////////////////
+ // TextAccessible
+
+ /**
+ * Return character count within the hypertext accessible.
+ */
+ uint32_t CharacterCount() const
+ { return GetChildOffset(ChildCount()); }
+
+ /**
+ * Get a character at the given offset (don't support magic offsets).
+ */
+ bool CharAt(int32_t aOffset, nsAString& aChar,
+ int32_t* aStartOffset = nullptr, int32_t* aEndOffset = nullptr)
+ {
+ NS_ASSERTION(!aStartOffset == !aEndOffset,
+ "Offsets should be both defined or both undefined!");
+
+ int32_t childIdx = GetChildIndexAtOffset(aOffset);
+ if (childIdx == -1)
+ return false;
+
+ Accessible* child = GetChildAt(childIdx);
+ child->AppendTextTo(aChar, aOffset - GetChildOffset(childIdx), 1);
+
+ if (aStartOffset && aEndOffset) {
+ *aStartOffset = aOffset;
+ *aEndOffset = aOffset + aChar.Length();
+ }
+ return true;
+ }
+
+ char16_t CharAt(int32_t aOffset)
+ {
+ nsAutoString charAtOffset;
+ CharAt(aOffset, charAtOffset);
+ return charAtOffset.CharAt(0);
+ }
+
+ /**
+ * Return true if char at the given offset equals to given char.
+ */
+ bool IsCharAt(int32_t aOffset, char16_t aChar)
+ { return CharAt(aOffset) == aChar; }
+
+ /**
+ * Return true if terminal char is at the given offset.
+ */
+ bool IsLineEndCharAt(int32_t aOffset)
+ { return IsCharAt(aOffset, '\n'); }
+
+ /**
+ * Return text between given offsets.
+ */
+ void TextSubstring(int32_t aStartOffset, int32_t aEndOffset, nsAString& aText);
+
+ /**
+ * Return text before/at/after the given offset corresponding to
+ * the boundary type.
+ */
+ void TextBeforeOffset(int32_t aOffset, AccessibleTextBoundary aBoundaryType,
+ int32_t* aStartOffset, int32_t* aEndOffset,
+ nsAString& aText);
+ void TextAtOffset(int32_t aOffset, AccessibleTextBoundary aBoundaryType,
+ int32_t* aStartOffset, int32_t* aEndOffset,
+ nsAString& aText);
+ void TextAfterOffset(int32_t aOffset, AccessibleTextBoundary aBoundaryType,
+ int32_t* aStartOffset, int32_t* aEndOffset,
+ nsAString& aText);
+
+ /**
+ * Return text attributes for the given text range.
+ */
+ already_AddRefed<nsIPersistentProperties>
+ TextAttributes(bool aIncludeDefAttrs, int32_t aOffset,
+ int32_t* aStartOffset, int32_t* aEndOffset);
+
+ /**
+ * Return text attributes applied to the accessible.
+ */
+ already_AddRefed<nsIPersistentProperties> DefaultTextAttributes();
+
+ /**
+ * Return text offset of the given child accessible within hypertext
+ * accessible.
+ *
+ * @param aChild [in] accessible child to get text offset for
+ * @param aInvalidateAfter [in, optional] indicates whether invalidate
+ * cached offsets for next siblings of the child
+ */
+ int32_t GetChildOffset(const Accessible* aChild,
+ bool aInvalidateAfter = false) const
+ {
+ int32_t index = GetIndexOf(aChild);
+ return index == -1 ? -1 : GetChildOffset(index, aInvalidateAfter);
+ }
+
+ /**
+ * Return text offset for the child accessible index.
+ */
+ int32_t GetChildOffset(uint32_t aChildIndex,
+ bool aInvalidateAfter = false) const;
+
+ /**
+ * Return child accessible at the given text offset.
+ *
+ * @param aOffset [in] the given text offset
+ */
+ int32_t GetChildIndexAtOffset(uint32_t aOffset) const;
+
+ /**
+ * Return child accessible at the given text offset.
+ *
+ * @param aOffset [in] the given text offset
+ */
+ Accessible* GetChildAtOffset(uint32_t aOffset) const
+ {
+ return GetChildAt(GetChildIndexAtOffset(aOffset));
+ }
+
+ /**
+ * Return true if the given offset/range is valid.
+ */
+ bool IsValidOffset(int32_t aOffset);
+ bool IsValidRange(int32_t aStartOffset, int32_t aEndOffset);
+
+ /**
+ * Return an offset at the given point.
+ */
+ int32_t OffsetAtPoint(int32_t aX, int32_t aY, uint32_t aCoordType);
+
+ /**
+ * Return a rect of the given text range relative given coordinate system.
+ */
+ nsIntRect TextBounds(int32_t aStartOffset, int32_t aEndOffset,
+ uint32_t aCoordType = nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE);
+
+ /**
+ * Return a rect for character at given offset relative given coordinate
+ * system.
+ */
+ nsIntRect CharBounds(int32_t aOffset, uint32_t aCoordType)
+ {
+ int32_t endOffset = aOffset == static_cast<int32_t>(CharacterCount()) ?
+ aOffset : aOffset + 1;
+ return TextBounds(aOffset, endOffset, aCoordType);
+ }
+
+ /**
+ * Get/set caret offset, if no caret then -1.
+ */
+ int32_t CaretOffset() const;
+ void SetCaretOffset(int32_t aOffset);
+
+ /**
+ * Provide the line number for the caret.
+ * @return 1-based index for the line number with the caret
+ */
+ int32_t CaretLineNumber();
+
+ /**
+ * Return the caret rect and the widget containing the caret within this
+ * text accessible.
+ *
+ * @param [out] the widget containing the caret
+ * @return the caret rect
+ */
+ mozilla::LayoutDeviceIntRect GetCaretRect(nsIWidget** aWidget);
+
+ /**
+ * Return selected regions count within the accessible.
+ */
+ int32_t SelectionCount();
+
+ /**
+ * Return the start and end offset of the specified selection.
+ */
+ bool SelectionBoundsAt(int32_t aSelectionNum,
+ int32_t* aStartOffset, int32_t* aEndOffset);
+
+ /*
+ * Changes the start and end offset of the specified selection.
+ * @return true if succeeded
+ */
+ bool SetSelectionBoundsAt(int32_t aSelectionNum,
+ int32_t aStartOffset, int32_t aEndOffset);
+
+ /**
+ * Adds a selection bounded by the specified offsets.
+ * @return true if succeeded
+ */
+ bool AddToSelection(int32_t aStartOffset, int32_t aEndOffset);
+
+ /*
+ * Removes the specified selection.
+ * @return true if succeeded
+ */
+ bool RemoveFromSelection(int32_t aSelectionNum);
+
+ /**
+ * Scroll the given text range into view.
+ */
+ void ScrollSubstringTo(int32_t aStartOffset, int32_t aEndOffset,
+ uint32_t aScrollType);
+
+ /**
+ * Scroll the given text range to the given point.
+ */
+ void ScrollSubstringToPoint(int32_t aStartOffset,
+ int32_t aEndOffset,
+ uint32_t aCoordinateType,
+ int32_t aX, int32_t aY);
+
+ /**
+ * Return a range that encloses the text control or the document this
+ * accessible belongs to.
+ */
+ void EnclosingRange(TextRange& aRange) const;
+
+ /**
+ * Return an array of disjoint ranges for selected text within the text control
+ * or the document this accessible belongs to.
+ */
+ void SelectionRanges(nsTArray<TextRange>* aRanges) const;
+
+ /**
+ * Return an array of disjoint ranges of visible text within the text control
+ * or the document this accessible belongs to.
+ */
+ void VisibleRanges(nsTArray<TextRange>* aRanges) const;
+
+ /**
+ * Return a range containing the given accessible.
+ */
+ void RangeByChild(Accessible* aChild, TextRange& aRange) const;
+
+ /**
+ * Return a range containing an accessible at the given point.
+ */
+ void RangeAtPoint(int32_t aX, int32_t aY, TextRange& aRange) const;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // EditableTextAccessible
+
+ void ReplaceText(const nsAString& aText);
+ void InsertText(const nsAString& aText, int32_t aPosition);
+ void CopyText(int32_t aStartPos, int32_t aEndPos);
+ void CutText(int32_t aStartPos, int32_t aEndPos);
+ void DeleteText(int32_t aStartPos, int32_t aEndPos);
+ void PasteText(int32_t aPosition);
+
+ /**
+ * Return the editor associated with the accessible.
+ */
+ virtual already_AddRefed<nsIEditor> GetEditor() const;
+
+ /**
+ * Return DOM selection object for the accessible.
+ */
+ dom::Selection* DOMSelection() const;
+
+protected:
+ virtual ~HyperTextAccessible() { }
+
+ // Accessible
+ virtual ENameValueFlag NativeName(nsString& aName) override;
+
+ // HyperTextAccessible
+
+ /**
+ * Transform magic offset into text offset.
+ */
+ index_t ConvertMagicOffset(int32_t aOffset) const;
+
+ /**
+ * Adjust an offset the caret stays at to get a text by line boundary.
+ */
+ uint32_t AdjustCaretOffset(uint32_t aOffset) const;
+
+ /**
+ * Return true if caret is at end of line.
+ */
+ bool IsCaretAtEndOfLine() const;
+
+ /**
+ * Return true if the given offset points to terminal empty line if any.
+ */
+ bool IsEmptyLastLineOffset(int32_t aOffset)
+ {
+ return aOffset == static_cast<int32_t>(CharacterCount()) &&
+ IsLineEndCharAt(aOffset - 1);
+ }
+
+ /**
+ * Return an offset of the found word boundary.
+ */
+ uint32_t FindWordBoundary(uint32_t aOffset, nsDirection aDirection,
+ EWordMovementType aWordMovementType)
+ {
+ return FindOffset(aOffset, aDirection, eSelectWord, aWordMovementType);
+ }
+
+ /**
+ * Used to get begin/end of previous/this/next line. Note: end of line
+ * is an offset right before '\n' character if any, the offset is right after
+ * '\n' character is begin of line. In case of wrap word breaks these offsets
+ * are equal.
+ */
+ enum EWhichLineBoundary {
+ ePrevLineBegin,
+ ePrevLineEnd,
+ eThisLineBegin,
+ eThisLineEnd,
+ eNextLineBegin,
+ eNextLineEnd
+ };
+
+ /**
+ * Return an offset for requested line boundary. See constants above.
+ */
+ uint32_t FindLineBoundary(uint32_t aOffset,
+ EWhichLineBoundary aWhichLineBoundary);
+
+ /**
+ * Return an offset corresponding to the given direction and selection amount
+ * relative the given offset. A helper used to find word or line boundaries.
+ */
+ uint32_t FindOffset(uint32_t aOffset, nsDirection aDirection,
+ nsSelectionAmount aAmount,
+ EWordMovementType aWordMovementType = eDefaultBehavior);
+
+ /**
+ * Return the boundaries of the substring in case of textual frame or
+ * frame boundaries in case of non textual frame, offsets are ignored.
+ */
+ nsIntRect GetBoundsInFrame(nsIFrame* aFrame,
+ uint32_t aStartRenderedOffset,
+ uint32_t aEndRenderedOffset);
+
+ // Selection helpers
+
+ /**
+ * Return frame selection object for the accessible.
+ */
+ already_AddRefed<nsFrameSelection> FrameSelection() const;
+
+ /**
+ * Return selection ranges within the accessible subtree.
+ */
+ void GetSelectionDOMRanges(SelectionType aSelectionType,
+ nsTArray<nsRange*>* aRanges);
+
+ nsresult SetSelectionRange(int32_t aStartPos, int32_t aEndPos);
+
+ /**
+ * Convert the given DOM point to a DOM point in non-generated contents.
+ *
+ * If aDOMPoint is in ::before, the result is immediately after it.
+ * If aDOMPoint is in ::after, the result is immediately before it.
+ *
+ * @param aDOMPoint [in] A DOM node and an index of its child. This may
+ * be in a generated content such as ::before or
+ * ::after.
+ * @param aElementContent [in] An nsIContent representing an element of
+ * aDOMPoint.node.
+ * @return An DOM point which must not be in generated
+ * contents.
+ */
+ DOMPoint ClosestNotGeneratedDOMPoint(const DOMPoint& aDOMPoint,
+ nsIContent* aElementContent);
+
+ // Helpers
+ nsresult GetDOMPointByFrameOffset(nsIFrame* aFrame, int32_t aOffset,
+ Accessible* aAccessible,
+ mozilla::a11y::DOMPoint* aPoint);
+
+ /**
+ * Set 'misspelled' text attribute and return range offsets where the
+ * attibute is stretched. If the text is not misspelled at the given offset
+ * then we expose only range offsets where text is not misspelled. The method
+ * is used by TextAttributes() method.
+ *
+ * @param aIncludeDefAttrs [in] points whether text attributes having default
+ * values of attributes should be included
+ * @param aSourceNode [in] the node we start to traverse from
+ * @param aStartOffset [in, out] the start offset
+ * @param aEndOffset [in, out] the end offset
+ * @param aAttributes [out, optional] result attributes
+ */
+ void GetSpellTextAttr(nsINode* aNode, int32_t aNodeOffset,
+ uint32_t* aStartOffset, uint32_t* aEndOffset,
+ nsIPersistentProperties* aAttributes);
+
+ /**
+ * Set xml-roles attributes for MathML elements.
+ * @param aAttributes
+ */
+ void SetMathMLXMLRoles(nsIPersistentProperties* aAttributes);
+
+private:
+ /**
+ * End text offsets array.
+ */
+ mutable nsTArray<uint32_t> mOffsets;
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible downcasting method
+
+inline HyperTextAccessible*
+Accessible::AsHyperText()
+{
+ return IsHyperText() ? static_cast<HyperTextAccessible*>(this) : nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/generic/ImageAccessible.cpp b/accessible/generic/ImageAccessible.cpp
new file mode 100644
index 000000000..c6556b04d
--- /dev/null
+++ b/accessible/generic/ImageAccessible.cpp
@@ -0,0 +1,224 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "ImageAccessible.h"
+
+#include "nsAccUtils.h"
+#include "Role.h"
+#include "AccIterator.h"
+#include "States.h"
+
+#include "imgIContainer.h"
+#include "imgIRequest.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIDocument.h"
+#include "nsIImageLoadingContent.h"
+#include "nsIPresShell.h"
+#include "nsIServiceManager.h"
+#include "nsIDOMHTMLImageElement.h"
+#include "nsIPersistentProperties2.h"
+#include "nsPIDOMWindow.h"
+#include "nsIURI.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// ImageAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+ImageAccessible::
+ ImageAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ LinkableAccessible(aContent, aDoc)
+{
+ mType = eImageType;
+}
+
+ImageAccessible::~ImageAccessible()
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible public
+
+uint64_t
+ImageAccessible::NativeState()
+{
+ // The state is a bitfield, get our inherited state, then logically OR it with
+ // states::ANIMATED if this is an animated image.
+
+ uint64_t state = LinkableAccessible::NativeState();
+
+ nsCOMPtr<nsIImageLoadingContent> content(do_QueryInterface(mContent));
+ nsCOMPtr<imgIRequest> imageRequest;
+
+ if (content)
+ content->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(imageRequest));
+
+ nsCOMPtr<imgIContainer> imgContainer;
+ if (imageRequest)
+ imageRequest->GetImage(getter_AddRefs(imgContainer));
+
+ if (imgContainer) {
+ bool animated;
+ imgContainer->GetAnimated(&animated);
+ if (animated)
+ state |= states::ANIMATED;
+ }
+
+ return state;
+}
+
+ENameValueFlag
+ImageAccessible::NativeName(nsString& aName)
+{
+ bool hasAltAttrib =
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::alt, aName);
+ if (!aName.IsEmpty())
+ return eNameOK;
+
+ ENameValueFlag nameFlag = Accessible::NativeName(aName);
+ if (!aName.IsEmpty())
+ return nameFlag;
+
+ // No accessible name but empty 'alt' attribute is present. If further name
+ // computation algorithm doesn't provide non empty name then it means
+ // an empty 'alt' attribute was used to indicate a decorative image (see
+ // Accessible::Name() method for details).
+ return hasAltAttrib ? eNoNameOnPurpose : eNameOK;
+}
+
+role
+ImageAccessible::NativeRole()
+{
+ return roles::GRAPHIC;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible
+
+uint8_t
+ImageAccessible::ActionCount()
+{
+ uint8_t actionCount = LinkableAccessible::ActionCount();
+ return HasLongDesc() ? actionCount + 1 : actionCount;
+}
+
+void
+ImageAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ aName.Truncate();
+ if (IsLongDescIndex(aIndex) && HasLongDesc())
+ aName.AssignLiteral("showlongdesc");
+ else
+ LinkableAccessible::ActionNameAt(aIndex, aName);
+}
+
+bool
+ImageAccessible::DoAction(uint8_t aIndex)
+{
+ // Get the long description uri and open in a new window.
+ if (!IsLongDescIndex(aIndex))
+ return LinkableAccessible::DoAction(aIndex);
+
+ nsCOMPtr<nsIURI> uri = GetLongDescURI();
+ if (!uri)
+ return false;
+
+ nsAutoCString utf8spec;
+ uri->GetSpec(utf8spec);
+ NS_ConvertUTF8toUTF16 spec(utf8spec);
+
+ nsIDocument* document = mContent->OwnerDoc();
+ nsCOMPtr<nsPIDOMWindowOuter> piWindow = document->GetWindow();
+ if (!piWindow)
+ return false;
+
+ nsCOMPtr<nsPIDOMWindowOuter> tmp;
+ return NS_SUCCEEDED(piWindow->Open(spec, EmptyString(), EmptyString(),
+ /* aLoadInfo = */ nullptr,
+ /* aForceNoOpener = */ false,
+ getter_AddRefs(tmp)));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ImageAccessible
+
+nsIntPoint
+ImageAccessible::Position(uint32_t aCoordType)
+{
+ nsIntRect rect = Bounds();
+ nsAccUtils::ConvertScreenCoordsTo(&rect.x, &rect.y, aCoordType, this);
+ return rect.TopLeft();
+}
+
+nsIntSize
+ImageAccessible::Size()
+{
+ return Bounds().Size();
+}
+
+// Accessible
+already_AddRefed<nsIPersistentProperties>
+ImageAccessible::NativeAttributes()
+{
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ LinkableAccessible::NativeAttributes();
+
+ nsAutoString src;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::src, src);
+ if (!src.IsEmpty())
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::src, src);
+
+ return attributes.forget();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Private methods
+
+already_AddRefed<nsIURI>
+ImageAccessible::GetLongDescURI() const
+{
+ if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::longdesc)) {
+ // To check if longdesc contains an invalid url.
+ nsAutoString longdesc;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::longdesc, longdesc);
+ if (longdesc.FindChar(' ') != -1 || longdesc.FindChar('\t') != -1 ||
+ longdesc.FindChar('\r') != -1 || longdesc.FindChar('\n') != -1) {
+ return nullptr;
+ }
+ nsCOMPtr<nsIURI> baseURI = mContent->GetBaseURI();
+ nsCOMPtr<nsIURI> uri;
+ nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), longdesc,
+ mContent->OwnerDoc(), baseURI);
+ return uri.forget();
+ }
+
+ DocAccessible* document = Document();
+ if (document) {
+ IDRefsIterator iter(document, mContent, nsGkAtoms::aria_describedby);
+ while (nsIContent* target = iter.NextElem()) {
+ if ((target->IsHTMLElement(nsGkAtoms::a) ||
+ target->IsHTMLElement(nsGkAtoms::area)) &&
+ target->HasAttr(kNameSpaceID_None, nsGkAtoms::href)) {
+ nsGenericHTMLElement* element =
+ nsGenericHTMLElement::FromContent(target);
+
+ nsCOMPtr<nsIURI> uri;
+ element->GetURIAttr(nsGkAtoms::href, nullptr, getter_AddRefs(uri));
+ return uri.forget();
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+bool
+ImageAccessible::IsLongDescIndex(uint8_t aIndex)
+{
+ return aIndex == LinkableAccessible::ActionCount();
+}
+
diff --git a/accessible/generic/ImageAccessible.h b/accessible/generic/ImageAccessible.h
new file mode 100644
index 000000000..f2324ae4c
--- /dev/null
+++ b/accessible/generic/ImageAccessible.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_ImageAccessible_h__
+#define mozilla_a11y_ImageAccessible_h__
+
+#include "BaseAccessibles.h"
+
+namespace mozilla {
+namespace a11y {
+
+/* Accessible for supporting images
+ * supports:
+ * - gets name, role
+ * - support basic state
+ */
+class ImageAccessible : public LinkableAccessible
+{
+public:
+ ImageAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+ virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+
+ // ImageAccessible
+ nsIntPoint Position(uint32_t aCoordType);
+ nsIntSize Size();
+
+protected:
+ virtual ~ImageAccessible();
+
+ // Accessible
+ virtual ENameValueFlag NativeName(nsString& aName) override;
+
+private:
+ /**
+ * Return whether the element has a longdesc URI.
+ */
+ bool HasLongDesc() const
+ {
+ nsCOMPtr<nsIURI> uri = GetLongDescURI();
+ return uri;
+ }
+
+ /**
+ * Return an URI for showlongdesc action if any.
+ */
+ already_AddRefed<nsIURI> GetLongDescURI() const;
+
+ /**
+ * Used by ActionNameAt and DoAction to ensure the index for opening the
+ * longdesc URL is valid.
+ * It is always assumed that the highest possible index opens the longdesc.
+ * This doesn't check that there is actually a longdesc, just that the index
+ * would be correct if there was one.
+ *
+ * @param aIndex The 0-based index to be tested.
+ *
+ * @returns true if index is valid for longdesc action.
+ */
+ inline bool IsLongDescIndex(uint8_t aIndex);
+
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible downcasting method
+
+inline ImageAccessible*
+Accessible::AsImage()
+{
+ return IsImage() ? static_cast<ImageAccessible*>(this) : nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/generic/OuterDocAccessible.cpp b/accessible/generic/OuterDocAccessible.cpp
new file mode 100644
index 000000000..a63069355
--- /dev/null
+++ b/accessible/generic/OuterDocAccessible.cpp
@@ -0,0 +1,218 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "OuterDocAccessible.h"
+
+#include "Accessible-inl.h"
+#include "nsAccUtils.h"
+#include "DocAccessible-inl.h"
+#include "mozilla/a11y/DocAccessibleParent.h"
+#include "mozilla/dom/TabParent.h"
+#include "Role.h"
+#include "States.h"
+
+#ifdef A11Y_LOG
+#include "Logging.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// OuterDocAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+OuterDocAccessible::
+ OuterDocAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+ mType = eOuterDocType;
+
+ // Request document accessible for the content document to make sure it's
+ // created. It will appended to outerdoc accessible children asynchronously.
+ nsIDocument* outerDoc = mContent->GetUncomposedDoc();
+ if (outerDoc) {
+ nsIDocument* innerDoc = outerDoc->GetSubDocumentFor(mContent);
+ if (innerDoc)
+ GetAccService()->GetDocAccessible(innerDoc);
+ }
+}
+
+OuterDocAccessible::~OuterDocAccessible()
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED0(OuterDocAccessible,
+ Accessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible public (DON'T add methods here)
+
+role
+OuterDocAccessible::NativeRole()
+{
+ return roles::INTERNAL_FRAME;
+}
+
+Accessible*
+OuterDocAccessible::ChildAtPoint(int32_t aX, int32_t aY,
+ EWhichChildAtPoint aWhichChild)
+{
+ nsIntRect docRect = Bounds();
+ if (aX < docRect.x || aX >= docRect.x + docRect.width ||
+ aY < docRect.y || aY >= docRect.y + docRect.height)
+ return nullptr;
+
+ // Always return the inner doc as direct child accessible unless bounds
+ // outside of it.
+ Accessible* child = GetChildAt(0);
+ NS_ENSURE_TRUE(child, nullptr);
+
+ if (aWhichChild == eDeepestChild)
+ return child->ChildAtPoint(aX, aY, eDeepestChild);
+ return child;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible public
+
+void
+OuterDocAccessible::Shutdown()
+{
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocDestroy))
+ logging::OuterDocDestroy(this);
+#endif
+
+ Accessible* child = mChildren.SafeElementAt(0, nullptr);
+ if (child) {
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocDestroy)) {
+ logging::DocDestroy("outerdoc's child document rebind is scheduled",
+ child->AsDoc()->DocumentNode());
+ }
+#endif
+ RemoveChild(child);
+
+ // XXX: sometimes outerdoc accessible is shutdown because of layout style
+ // change however the presshell of underlying document isn't destroyed and
+ // the document doesn't get pagehide events. Schedule a document rebind
+ // to its parent document. Otherwise a document accessible may be lost if
+ // its outerdoc has being recreated (see bug 862863 for details).
+ if (!mDoc->IsDefunct()) {
+ mDoc->BindChildDocument(child->AsDoc());
+ }
+ }
+
+ AccessibleWrap::Shutdown();
+}
+
+bool
+OuterDocAccessible::InsertChildAt(uint32_t aIdx, Accessible* aAccessible)
+{
+ MOZ_RELEASE_ASSERT(aAccessible->IsDoc(),
+ "OuterDocAccessible can have a document child only!");
+
+ // We keep showing the old document for a bit after creating the new one,
+ // and while building the new DOM and frame tree. That's done on purpose
+ // to avoid weird flashes of default background color.
+ // The old viewer will be destroyed after the new one is created.
+ // For a11y, it should be safe to shut down the old document now.
+ if (mChildren.Length())
+ mChildren[0]->Shutdown();
+
+ if (!AccessibleWrap::InsertChildAt(0, aAccessible))
+ return false;
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocCreate)) {
+ logging::DocCreate("append document to outerdoc",
+ aAccessible->AsDoc()->DocumentNode());
+ logging::Address("outerdoc", this);
+ }
+#endif
+
+ return true;
+}
+
+bool
+OuterDocAccessible::RemoveChild(Accessible* aAccessible)
+{
+ Accessible* child = mChildren.SafeElementAt(0, nullptr);
+ if (child != aAccessible) {
+ NS_ERROR("Wrong child to remove!");
+ return false;
+ }
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDocDestroy)) {
+ logging::DocDestroy("remove document from outerdoc",
+ child->AsDoc()->DocumentNode(), child->AsDoc());
+ logging::Address("outerdoc", this);
+ }
+#endif
+
+ bool wasRemoved = AccessibleWrap::RemoveChild(child);
+
+ NS_ASSERTION(!mChildren.Length(),
+ "This child document of outerdoc accessible wasn't removed!");
+
+ return wasRemoved;
+}
+
+bool
+OuterDocAccessible::IsAcceptableChild(nsIContent* aEl) const
+{
+ // outer document accessible doesn't not participate in ordinal tree
+ // mutations.
+ return false;
+}
+
+#if defined(XP_WIN)
+
+// On Windows e10s, since we don't cache in the chrome process, these next two
+// functions must be implemented so that we properly cross the chrome-to-content
+// boundary when traversing.
+
+uint32_t
+OuterDocAccessible::ChildCount() const
+{
+ uint32_t result = mChildren.Length();
+ if (!result && RemoteChildDoc()) {
+ result = 1;
+ }
+ return result;
+}
+
+Accessible*
+OuterDocAccessible::GetChildAt(uint32_t aIndex) const
+{
+ Accessible* result = AccessibleWrap::GetChildAt(aIndex);
+ if (result || aIndex) {
+ return result;
+ }
+ // If we are asking for child 0 and GetChildAt doesn't return anything, try
+ // to get the remote child doc and return that instead.
+ ProxyAccessible* remoteChild = RemoteChildDoc();
+ if (!remoteChild) {
+ return nullptr;
+ }
+ return WrapperFor(remoteChild);
+}
+
+#endif // defined(XP_WIN)
+
+ProxyAccessible*
+OuterDocAccessible::RemoteChildDoc() const
+{
+ dom::TabParent* tab = dom::TabParent::GetFrom(GetContent());
+ if (!tab)
+ return nullptr;
+
+ return tab->GetTopLevelDocAccessible();
+}
diff --git a/accessible/generic/OuterDocAccessible.h b/accessible/generic/OuterDocAccessible.h
new file mode 100644
index 000000000..bfa02ba14
--- /dev/null
+++ b/accessible/generic/OuterDocAccessible.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_OUTERDOCACCESSIBLE_H_
+#define MOZILLA_A11Y_OUTERDOCACCESSIBLE_H_
+
+#include "AccessibleWrap.h"
+
+namespace mozilla {
+namespace a11y {
+class ProxyAccessible;
+
+/**
+ * Used for <browser>, <frame>, <iframe>, <page> or editor> elements.
+ *
+ * In these variable names, "outer" relates to the OuterDocAccessible as
+ * opposed to the DocAccessibleWrap which is "inner". The outer node is
+ * a something like tags listed above, whereas the inner node corresponds to
+ * the inner document root.
+ */
+
+class OuterDocAccessible final : public AccessibleWrap
+{
+public:
+ OuterDocAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ ProxyAccessible* RemoteChildDoc() const;
+
+ // Accessible
+ virtual void Shutdown() override;
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual Accessible* ChildAtPoint(int32_t aX, int32_t aY,
+ EWhichChildAtPoint aWhichChild) override;
+
+ virtual bool InsertChildAt(uint32_t aIdx, Accessible* aChild) override;
+ virtual bool RemoveChild(Accessible* aAccessible) override;
+ virtual bool IsAcceptableChild(nsIContent* aEl) const override;
+
+#if defined(XP_WIN)
+ virtual uint32_t ChildCount() const override;
+ virtual Accessible* GetChildAt(uint32_t aIndex) const override;
+#endif // defined(XP_WIN)
+
+protected:
+ virtual ~OuterDocAccessible() override;
+};
+
+inline OuterDocAccessible*
+Accessible::AsOuterDoc()
+{
+ return IsOuterDoc() ? static_cast<OuterDocAccessible*>(this) : nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/generic/RootAccessible.cpp b/accessible/generic/RootAccessible.cpp
new file mode 100644
index 000000000..5817f2da9
--- /dev/null
+++ b/accessible/generic/RootAccessible.cpp
@@ -0,0 +1,732 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "RootAccessible.h"
+
+#include "mozilla/ArrayUtils.h"
+
+#define CreateEvent CreateEventA
+#include "nsIDOMDocument.h"
+
+#include "Accessible-inl.h"
+#include "DocAccessible-inl.h"
+#include "nsAccessibilityService.h"
+#include "nsAccUtils.h"
+#include "nsCoreUtils.h"
+#include "nsEventShell.h"
+#include "Relation.h"
+#include "Role.h"
+#include "States.h"
+#ifdef MOZ_XUL
+#include "XULTreeAccessible.h"
+#endif
+
+#include "mozilla/dom/Element.h"
+
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocShellTreeOwner.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/EventTarget.h"
+#include "nsIDOMCustomEvent.h"
+#include "nsIDOMXULMultSelectCntrlEl.h"
+#include "nsIDocument.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIPropertyBag2.h"
+#include "nsIServiceManager.h"
+#include "nsPIDOMWindow.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsReadableUtils.h"
+#include "nsFocusManager.h"
+#include "nsGlobalWindow.h"
+
+#ifdef MOZ_XUL
+#include "nsIXULDocument.h"
+#include "nsIXULWindow.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+using namespace mozilla::dom;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED0(RootAccessible, DocAccessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// Constructor/destructor
+
+RootAccessible::
+ RootAccessible(nsIDocument* aDocument, nsIPresShell* aPresShell) :
+ DocAccessibleWrap(aDocument, aPresShell)
+{
+ mType = eRootType;
+}
+
+RootAccessible::~RootAccessible()
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible
+
+ENameValueFlag
+RootAccessible::Name(nsString& aName)
+{
+ aName.Truncate();
+
+ if (ARIARoleMap()) {
+ Accessible::Name(aName);
+ if (!aName.IsEmpty())
+ return eNameOK;
+ }
+
+ mDocumentNode->GetTitle(aName);
+ return eNameOK;
+}
+
+role
+RootAccessible::NativeRole()
+{
+ // If it's a <dialog> or <wizard>, use roles::DIALOG instead
+ dom::Element* rootElm = mDocumentNode->GetRootElement();
+ if (rootElm && rootElm->IsAnyOfXULElements(nsGkAtoms::dialog,
+ nsGkAtoms::wizard))
+ return roles::DIALOG;
+
+ return DocAccessibleWrap::NativeRole();
+}
+
+// RootAccessible protected member
+#ifdef MOZ_XUL
+uint32_t
+RootAccessible::GetChromeFlags()
+{
+ // Return the flag set for the top level window as defined
+ // by nsIWebBrowserChrome::CHROME_WINDOW_[FLAGNAME]
+ // Not simple: nsIXULWindow is not just a QI from nsIDOMWindow
+ nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(mDocumentNode);
+ NS_ENSURE_TRUE(docShell, 0);
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+ docShell->GetTreeOwner(getter_AddRefs(treeOwner));
+ NS_ENSURE_TRUE(treeOwner, 0);
+ nsCOMPtr<nsIXULWindow> xulWin(do_GetInterface(treeOwner));
+ if (!xulWin) {
+ return 0;
+ }
+ uint32_t chromeFlags;
+ xulWin->GetChromeFlags(&chromeFlags);
+ return chromeFlags;
+}
+#endif
+
+uint64_t
+RootAccessible::NativeState()
+{
+ uint64_t state = DocAccessibleWrap::NativeState();
+ if (state & states::DEFUNCT)
+ return state;
+
+#ifdef MOZ_XUL
+ uint32_t chromeFlags = GetChromeFlags();
+ if (chromeFlags & nsIWebBrowserChrome::CHROME_WINDOW_RESIZE)
+ state |= states::SIZEABLE;
+ // If it has a titlebar it's movable
+ // XXX unless it's minimized or maximized, but not sure
+ // how to detect that
+ if (chromeFlags & nsIWebBrowserChrome::CHROME_TITLEBAR)
+ state |= states::MOVEABLE;
+ if (chromeFlags & nsIWebBrowserChrome::CHROME_MODAL)
+ state |= states::MODAL;
+#endif
+
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm && fm->GetActiveWindow() == mDocumentNode->GetWindow())
+ state |= states::ACTIVE;
+
+ return state;
+}
+
+const char* const kEventTypes[] = {
+#ifdef DEBUG_DRAGDROPSTART
+ // Capture mouse over events and fire fake DRAGDROPSTART event to simplify
+ // debugging a11y objects with event viewers.
+ "mouseover",
+#endif
+ // Fired when list or tree selection changes.
+ "select",
+ // Fired when value changes immediately, wether or not focused changed.
+ "ValueChange",
+ "AlertActive",
+ "TreeRowCountChanged",
+ "TreeInvalidated",
+ // add ourself as a OpenStateChange listener (custom event fired in tree.xml)
+ "OpenStateChange",
+ // add ourself as a CheckboxStateChange listener (custom event fired in HTMLInputElement.cpp)
+ "CheckboxStateChange",
+ // add ourself as a RadioStateChange Listener ( custom event fired in in HTMLInputElement.cpp & radio.xml)
+ "RadioStateChange",
+ "popupshown",
+ "popuphiding",
+ "DOMMenuInactive",
+ "DOMMenuItemActive",
+ "DOMMenuItemInactive",
+ "DOMMenuBarActive",
+ "DOMMenuBarInactive"
+};
+
+nsresult
+RootAccessible::AddEventListeners()
+{
+ // EventTarget interface allows to register event listeners to
+ // receive untrusted events (synthetic events generated by untrusted code).
+ // For example, XBL bindings implementations for elements that are hosted in
+ // non chrome document fire untrusted events.
+ nsCOMPtr<EventTarget> nstarget = mDocumentNode;
+
+ if (nstarget) {
+ for (const char* const* e = kEventTypes,
+ * const* e_end = ArrayEnd(kEventTypes);
+ e < e_end; ++e) {
+ nsresult rv = nstarget->AddEventListener(NS_ConvertASCIItoUTF16(*e),
+ this, true, true, 2);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return DocAccessible::AddEventListeners();
+}
+
+nsresult
+RootAccessible::RemoveEventListeners()
+{
+ nsCOMPtr<EventTarget> target = mDocumentNode;
+ if (target) {
+ for (const char* const* e = kEventTypes,
+ * const* e_end = ArrayEnd(kEventTypes);
+ e < e_end; ++e) {
+ nsresult rv = target->RemoveEventListener(NS_ConvertASCIItoUTF16(*e), this, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // Do this before removing clearing caret accessible, so that it can use
+ // shutdown the caret accessible's selection listener
+ DocAccessible::RemoveEventListeners();
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// public
+
+void
+RootAccessible::DocumentActivated(DocAccessible* aDocument)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIDOMEventListener
+
+NS_IMETHODIMP
+RootAccessible::HandleEvent(nsIDOMEvent* aDOMEvent)
+{
+ MOZ_ASSERT(aDOMEvent);
+ Event* event = aDOMEvent->InternalDOMEvent();
+ nsCOMPtr<nsINode> origTargetNode = do_QueryInterface(event->GetOriginalTarget());
+ if (!origTargetNode)
+ return NS_OK;
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDOMEvents)) {
+ nsAutoString eventType;
+ aDOMEvent->GetType(eventType);
+ logging::DOMEvent("handled", origTargetNode, eventType);
+ }
+#endif
+
+ DocAccessible* document =
+ GetAccService()->GetDocAccessible(origTargetNode->OwnerDoc());
+
+ if (document) {
+ // Root accessible exists longer than any of its descendant documents so
+ // that we are guaranteed notification is processed before root accessible
+ // is destroyed.
+ document->HandleNotification<RootAccessible, nsIDOMEvent>
+ (this, &RootAccessible::ProcessDOMEvent, aDOMEvent);
+ }
+
+ return NS_OK;
+}
+
+// RootAccessible protected
+void
+RootAccessible::ProcessDOMEvent(nsIDOMEvent* aDOMEvent)
+{
+ MOZ_ASSERT(aDOMEvent);
+ Event* event = aDOMEvent->InternalDOMEvent();
+ nsCOMPtr<nsINode> origTargetNode = do_QueryInterface(event->GetOriginalTarget());
+
+ nsAutoString eventType;
+ aDOMEvent->GetType(eventType);
+
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eDOMEvents))
+ logging::DOMEvent("processed", origTargetNode, eventType);
+#endif
+
+ if (eventType.EqualsLiteral("popuphiding")) {
+ HandlePopupHidingEvent(origTargetNode);
+ return;
+ }
+
+ DocAccessible* targetDocument = GetAccService()->
+ GetDocAccessible(origTargetNode->OwnerDoc());
+ NS_ASSERTION(targetDocument, "No document while accessible is in document?!");
+
+ Accessible* accessible =
+ targetDocument->GetAccessibleOrContainer(origTargetNode);
+ if (!accessible)
+ return;
+
+#ifdef MOZ_XUL
+ XULTreeAccessible* treeAcc = accessible->AsXULTree();
+ if (treeAcc) {
+ if (eventType.EqualsLiteral("TreeRowCountChanged")) {
+ HandleTreeRowCountChangedEvent(aDOMEvent, treeAcc);
+ return;
+ }
+
+ if (eventType.EqualsLiteral("TreeInvalidated")) {
+ HandleTreeInvalidatedEvent(aDOMEvent, treeAcc);
+ return;
+ }
+ }
+#endif
+
+ if (eventType.EqualsLiteral("RadioStateChange")) {
+ uint64_t state = accessible->State();
+ bool isEnabled = (state & (states::CHECKED | states::SELECTED)) != 0;
+
+ if (accessible->NeedsDOMUIEvent()) {
+ RefPtr<AccEvent> accEvent =
+ new AccStateChangeEvent(accessible, states::CHECKED, isEnabled);
+ nsEventShell::FireEvent(accEvent);
+ }
+
+ if (isEnabled) {
+ FocusMgr()->ActiveItemChanged(accessible);
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::ActiveItemChangeCausedBy("RadioStateChange", accessible);
+#endif
+ }
+
+ return;
+ }
+
+ if (eventType.EqualsLiteral("CheckboxStateChange")) {
+ if (accessible->NeedsDOMUIEvent()) {
+ uint64_t state = accessible->State();
+ bool isEnabled = !!(state & states::CHECKED);
+
+ RefPtr<AccEvent> accEvent =
+ new AccStateChangeEvent(accessible, states::CHECKED, isEnabled);
+ nsEventShell::FireEvent(accEvent);
+ }
+ return;
+ }
+
+ Accessible* treeItemAcc = nullptr;
+#ifdef MOZ_XUL
+ // If it's a tree element, need the currently selected item.
+ if (treeAcc) {
+ treeItemAcc = accessible->CurrentItem();
+ if (treeItemAcc)
+ accessible = treeItemAcc;
+ }
+
+ if (treeItemAcc && eventType.EqualsLiteral("OpenStateChange")) {
+ uint64_t state = accessible->State();
+ bool isEnabled = (state & states::EXPANDED) != 0;
+
+ RefPtr<AccEvent> accEvent =
+ new AccStateChangeEvent(accessible, states::EXPANDED, isEnabled);
+ nsEventShell::FireEvent(accEvent);
+ return;
+ }
+
+ nsINode* targetNode = accessible->GetNode();
+ if (treeItemAcc && eventType.EqualsLiteral("select")) {
+ // XXX: We shouldn't be based on DOM select event which doesn't provide us
+ // any context info. We should integrate into nsTreeSelection instead.
+ // If multiselect tree, we should fire selectionadd or selection removed
+ if (FocusMgr()->HasDOMFocus(targetNode)) {
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSel =
+ do_QueryInterface(targetNode);
+ nsAutoString selType;
+ multiSel->GetSelType(selType);
+ if (selType.IsEmpty() || !selType.EqualsLiteral("single")) {
+ // XXX: We need to fire EVENT_SELECTION_ADD and EVENT_SELECTION_REMOVE
+ // for each tree item. Perhaps each tree item will need to cache its
+ // selection state and fire an event after a DOM "select" event when
+ // that state changes. XULTreeAccessible::UpdateTreeSelection();
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN,
+ accessible);
+ return;
+ }
+
+ RefPtr<AccSelChangeEvent> selChangeEvent =
+ new AccSelChangeEvent(treeAcc, treeItemAcc,
+ AccSelChangeEvent::eSelectionAdd);
+ nsEventShell::FireEvent(selChangeEvent);
+ return;
+ }
+ }
+ else
+#endif
+ if (eventType.EqualsLiteral("AlertActive")) {
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_ALERT, accessible);
+ }
+ else if (eventType.EqualsLiteral("popupshown")) {
+ HandlePopupShownEvent(accessible);
+ }
+ else if (eventType.EqualsLiteral("DOMMenuInactive")) {
+ if (accessible->Role() == roles::MENUPOPUP) {
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END,
+ accessible);
+ }
+ }
+ else if (eventType.EqualsLiteral("DOMMenuItemActive")) {
+ FocusMgr()->ActiveItemChanged(accessible);
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::ActiveItemChangeCausedBy("DOMMenuItemActive", accessible);
+#endif
+ }
+ else if (eventType.EqualsLiteral("DOMMenuItemInactive")) {
+ // Process DOMMenuItemInactive event for autocomplete only because this is
+ // unique widget that may acquire focus from autocomplete popup while popup
+ // stays open and has no active item. In case of XUL tree autocomplete
+ // popup this event is fired for tree accessible.
+ Accessible* widget =
+ accessible->IsWidget() ? accessible : accessible->ContainerWidget();
+ if (widget && widget->IsAutoCompletePopup()) {
+ FocusMgr()->ActiveItemChanged(nullptr);
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::ActiveItemChangeCausedBy("DOMMenuItemInactive", accessible);
+#endif
+ }
+ }
+ else if (eventType.EqualsLiteral("DOMMenuBarActive")) { // Always from user input
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENU_START,
+ accessible, eFromUserInput);
+
+ // Notify of active item change when menubar gets active and if it has
+ // current item. This is a case of mouseover (set current menuitem) and
+ // mouse click (activate the menubar). If menubar doesn't have current item
+ // (can be a case of menubar activation from keyboard) then ignore this
+ // notification because later we'll receive DOMMenuItemActive event after
+ // current menuitem is set.
+ Accessible* activeItem = accessible->CurrentItem();
+ if (activeItem) {
+ FocusMgr()->ActiveItemChanged(activeItem);
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::ActiveItemChangeCausedBy("DOMMenuBarActive", accessible);
+#endif
+ }
+ }
+ else if (eventType.EqualsLiteral("DOMMenuBarInactive")) { // Always from user input
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENU_END,
+ accessible, eFromUserInput);
+
+ FocusMgr()->ActiveItemChanged(nullptr);
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::ActiveItemChangeCausedBy("DOMMenuBarInactive", accessible);
+#endif
+ }
+ else if (accessible->NeedsDOMUIEvent() &&
+ eventType.EqualsLiteral("ValueChange")) {
+ uint32_t event = accessible->HasNumericValue()
+ ? nsIAccessibleEvent::EVENT_VALUE_CHANGE
+ : nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE;
+ targetDocument->FireDelayedEvent(event, accessible);
+ }
+#ifdef DEBUG_DRAGDROPSTART
+ else if (eventType.EqualsLiteral("mouseover")) {
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_DRAGDROP_START,
+ accessible);
+ }
+#endif
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible
+
+void
+RootAccessible::Shutdown()
+{
+ // Called manually or by Accessible::LastRelease()
+ if (!PresShell())
+ return; // Already shutdown
+
+ DocAccessibleWrap::Shutdown();
+}
+
+Relation
+RootAccessible::RelationByType(RelationType aType)
+{
+ if (!mDocumentNode || aType != RelationType::EMBEDS)
+ return DocAccessibleWrap::RelationByType(aType);
+
+ if (nsPIDOMWindowOuter* rootWindow = mDocumentNode->GetWindow()) {
+ nsCOMPtr<nsPIDOMWindowOuter> contentWindow = nsGlobalWindow::Cast(rootWindow)->GetContent();
+ if (contentWindow) {
+ nsCOMPtr<nsIDocument> contentDocumentNode = contentWindow->GetDoc();
+ if (contentDocumentNode) {
+ DocAccessible* contentDocument =
+ GetAccService()->GetDocAccessible(contentDocumentNode);
+ if (contentDocument)
+ return Relation(contentDocument);
+ }
+ }
+ }
+
+ return Relation();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Protected members
+
+void
+RootAccessible::HandlePopupShownEvent(Accessible* aAccessible)
+{
+ roles::Role role = aAccessible->Role();
+
+ if (role == roles::MENUPOPUP) {
+ // Don't fire menupopup events for combobox and autocomplete lists.
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START,
+ aAccessible);
+ return;
+ }
+
+ if (role == roles::TOOLTIP) {
+ // There is a single <xul:tooltip> node which Mozilla moves around.
+ // The accessible for it stays the same no matter where it moves.
+ // AT's expect to get an EVENT_SHOW for the tooltip.
+ // In event callback the tooltip's accessible will be ready.
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SHOW, aAccessible);
+ return;
+ }
+
+ if (role == roles::COMBOBOX_LIST) {
+ // Fire expanded state change event for comboboxes and autocompeletes.
+ Accessible* combobox = aAccessible->Parent();
+ if (!combobox)
+ return;
+
+ roles::Role comboboxRole = combobox->Role();
+ if (comboboxRole == roles::COMBOBOX ||
+ comboboxRole == roles::AUTOCOMPLETE) {
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(combobox, states::EXPANDED, true);
+ if (event)
+ nsEventShell::FireEvent(event);
+ }
+ }
+}
+
+void
+RootAccessible::HandlePopupHidingEvent(nsINode* aPopupNode)
+{
+ // Get popup accessible. There are cases when popup element isn't accessible
+ // but an underlying widget is and behaves like popup, an example is
+ // autocomplete popups.
+ DocAccessible* document = nsAccUtils::GetDocAccessibleFor(aPopupNode);
+ if (!document)
+ return;
+
+ Accessible* popup = document->GetAccessible(aPopupNode);
+ if (!popup) {
+ Accessible* popupContainer = document->GetContainerAccessible(aPopupNode);
+ if (!popupContainer)
+ return;
+
+ uint32_t childCount = popupContainer->ChildCount();
+ for (uint32_t idx = 0; idx < childCount; idx++) {
+ Accessible* child = popupContainer->GetChildAt(idx);
+ if (child->IsAutoCompletePopup()) {
+ popup = child;
+ break;
+ }
+ }
+
+ // No popup no events. Focus is managed by DOM. This is a case for
+ // menupopups of menus on Linux since there are no accessible for popups.
+ if (!popup)
+ return;
+ }
+
+ // In case of autocompletes and comboboxes fire state change event for
+ // expanded state. Note, HTML form autocomplete isn't a subject of state
+ // change event because they aren't autocompletes strictly speaking.
+ // When popup closes (except nested popups and menus) then fire focus event to
+ // where it was. The focus event is expected even if popup didn't take a focus.
+
+ static const uint32_t kNotifyOfFocus = 1;
+ static const uint32_t kNotifyOfState = 2;
+ uint32_t notifyOf = 0;
+
+ // HTML select is target of popuphidding event. Otherwise get container
+ // widget. No container widget means this is either tooltip or menupopup.
+ // No events in the former case.
+ Accessible* widget = nullptr;
+ if (popup->IsCombobox()) {
+ widget = popup;
+ } else {
+ widget = popup->ContainerWidget();
+ if (!widget) {
+ if (!popup->IsMenuPopup())
+ return;
+
+ widget = popup;
+ }
+ }
+
+ if (popup->IsAutoCompletePopup()) {
+ // No focus event for autocomplete because it's managed by
+ // DOMMenuItemInactive events.
+ if (widget->IsAutoComplete())
+ notifyOf = kNotifyOfState;
+
+ } else if (widget->IsCombobox()) {
+ // Fire focus for active combobox, otherwise the focus is managed by DOM
+ // focus notifications. Always fire state change event.
+ if (widget->IsActiveWidget())
+ notifyOf = kNotifyOfFocus;
+ notifyOf |= kNotifyOfState;
+
+ } else if (widget->IsMenuButton()) {
+ // Can be a part of autocomplete.
+ Accessible* compositeWidget = widget->ContainerWidget();
+ if (compositeWidget && compositeWidget->IsAutoComplete()) {
+ widget = compositeWidget;
+ notifyOf = kNotifyOfState;
+ }
+
+ // Autocomplete (like searchbar) can be inactive when popup hiddens
+ notifyOf |= kNotifyOfFocus;
+
+ } else if (widget == popup) {
+ // Top level context menus and alerts.
+ // Ignore submenus and menubar. When submenu is closed then sumbenu
+ // container menuitem takes a focus via DOMMenuItemActive notification.
+ // For menubars processing we listen DOMMenubarActive/Inactive
+ // notifications.
+ notifyOf = kNotifyOfFocus;
+ }
+
+ // Restore focus to where it was.
+ if (notifyOf & kNotifyOfFocus) {
+ FocusMgr()->ActiveItemChanged(nullptr);
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eFocus))
+ logging::ActiveItemChangeCausedBy("popuphiding", popup);
+#endif
+ }
+
+ // Fire expanded state change event.
+ if (notifyOf & kNotifyOfState) {
+ RefPtr<AccEvent> event =
+ new AccStateChangeEvent(widget, states::EXPANDED, false);
+ document->FireDelayedEvent(event);
+ }
+}
+
+#ifdef MOZ_XUL
+void
+RootAccessible::HandleTreeRowCountChangedEvent(nsIDOMEvent* aEvent,
+ XULTreeAccessible* aAccessible)
+{
+ nsCOMPtr<nsIDOMCustomEvent> customEvent(do_QueryInterface(aEvent));
+ if (!customEvent)
+ return;
+
+ nsCOMPtr<nsIVariant> detailVariant;
+ customEvent->GetDetail(getter_AddRefs(detailVariant));
+ if (!detailVariant)
+ return;
+
+ nsCOMPtr<nsISupports> supports;
+ detailVariant->GetAsISupports(getter_AddRefs(supports));
+ nsCOMPtr<nsIPropertyBag2> propBag(do_QueryInterface(supports));
+ if (!propBag)
+ return;
+
+ nsresult rv;
+ int32_t index, count;
+ rv = propBag->GetPropertyAsInt32(NS_LITERAL_STRING("index"), &index);
+ if (NS_FAILED(rv))
+ return;
+
+ rv = propBag->GetPropertyAsInt32(NS_LITERAL_STRING("count"), &count);
+ if (NS_FAILED(rv))
+ return;
+
+ aAccessible->InvalidateCache(index, count);
+}
+
+void
+RootAccessible::HandleTreeInvalidatedEvent(nsIDOMEvent* aEvent,
+ XULTreeAccessible* aAccessible)
+{
+ nsCOMPtr<nsIDOMCustomEvent> customEvent(do_QueryInterface(aEvent));
+ if (!customEvent)
+ return;
+
+ nsCOMPtr<nsIVariant> detailVariant;
+ customEvent->GetDetail(getter_AddRefs(detailVariant));
+ if (!detailVariant)
+ return;
+
+ nsCOMPtr<nsISupports> supports;
+ detailVariant->GetAsISupports(getter_AddRefs(supports));
+ nsCOMPtr<nsIPropertyBag2> propBag(do_QueryInterface(supports));
+ if (!propBag)
+ return;
+
+ int32_t startRow = 0, endRow = -1, startCol = 0, endCol = -1;
+ propBag->GetPropertyAsInt32(NS_LITERAL_STRING("startrow"),
+ &startRow);
+ propBag->GetPropertyAsInt32(NS_LITERAL_STRING("endrow"),
+ &endRow);
+ propBag->GetPropertyAsInt32(NS_LITERAL_STRING("startcolumn"),
+ &startCol);
+ propBag->GetPropertyAsInt32(NS_LITERAL_STRING("endcolumn"),
+ &endCol);
+
+ aAccessible->TreeViewInvalidated(startRow, endRow, startCol, endCol);
+}
+#endif
+
+ProxyAccessible*
+RootAccessible::GetPrimaryRemoteTopLevelContentDoc() const
+{
+ nsCOMPtr<nsIDocShellTreeOwner> owner;
+ mDocumentNode->GetDocShell()->GetTreeOwner(getter_AddRefs(owner));
+ NS_ENSURE_TRUE(owner, nullptr);
+
+ nsCOMPtr<nsITabParent> tabParent;
+ owner->GetPrimaryTabParent(getter_AddRefs(tabParent));
+ if (!tabParent) {
+ return nullptr;
+ }
+
+ auto tab = static_cast<dom::TabParent*>(tabParent.get());
+ return tab->GetTopLevelDocAccessible();
+}
diff --git a/accessible/generic/RootAccessible.h b/accessible/generic/RootAccessible.h
new file mode 100644
index 000000000..beb74cf4b
--- /dev/null
+++ b/accessible/generic/RootAccessible.h
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_RootAccessible_h__
+#define mozilla_a11y_RootAccessible_h__
+
+#include "HyperTextAccessible.h"
+#include "DocAccessibleWrap.h"
+
+#include "nsIDOMEventListener.h"
+
+class nsIDocument;
+
+namespace mozilla {
+namespace a11y {
+
+class RootAccessible : public DocAccessibleWrap,
+ public nsIDOMEventListener
+{
+ NS_DECL_ISUPPORTS_INHERITED
+
+public:
+ RootAccessible(nsIDocument* aDocument, nsIPresShell* aPresShell);
+
+ // nsIDOMEventListener
+ NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override;
+
+ // Accessible
+ virtual void Shutdown() override;
+ virtual mozilla::a11y::ENameValueFlag Name(nsString& aName) override;
+ virtual Relation RelationByType(RelationType aType) override;
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+
+ // RootAccessible
+
+ /**
+ * Notify that the sub document presshell was activated.
+ */
+ virtual void DocumentActivated(DocAccessible* aDocument);
+
+ /**
+ * Return the primary remote top level document if any.
+ */
+ ProxyAccessible* GetPrimaryRemoteTopLevelContentDoc() const;
+
+protected:
+ virtual ~RootAccessible();
+
+ /**
+ * Add/remove DOM event listeners.
+ */
+ virtual nsresult AddEventListeners() override;
+ virtual nsresult RemoveEventListeners() override;
+
+ /**
+ * Process the DOM event.
+ */
+ void ProcessDOMEvent(nsIDOMEvent* aEvent);
+
+ /**
+ * Process "popupshown" event. Used by HandleEvent().
+ */
+ void HandlePopupShownEvent(Accessible* aAccessible);
+
+ /*
+ * Process "popuphiding" event. Used by HandleEvent().
+ */
+ void HandlePopupHidingEvent(nsINode* aNode);
+
+#ifdef MOZ_XUL
+ void HandleTreeRowCountChangedEvent(nsIDOMEvent* aEvent,
+ XULTreeAccessible* aAccessible);
+ void HandleTreeInvalidatedEvent(nsIDOMEvent* aEvent,
+ XULTreeAccessible* aAccessible);
+
+ uint32_t GetChromeFlags();
+#endif
+};
+
+inline RootAccessible*
+Accessible::AsRoot()
+{
+ return IsRoot() ? static_cast<mozilla::a11y::RootAccessible*>(this) : nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/generic/TableAccessible.h b/accessible/generic/TableAccessible.h
new file mode 100644
index 000000000..fbb8393b6
--- /dev/null
+++ b/accessible/generic/TableAccessible.h
@@ -0,0 +1,187 @@
+/* -*- 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 TABLE_ACCESSIBLE_H
+#define TABLE_ACCESSIBLE_H
+
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+
+/**
+ * Accessible table interface.
+ */
+class TableAccessible
+{
+public:
+
+ /**
+ * Return the caption accessible if any for this table.
+ */
+ virtual Accessible* Caption() const { return nullptr; }
+
+ /**
+ * Get the summary for this table.
+ */
+ virtual void Summary(nsString& aSummary) { aSummary.Truncate(); }
+
+ /**
+ * Return the number of columns in the table.
+ */
+ virtual uint32_t ColCount() { return 0; }
+
+ /**
+ * Return the number of rows in the table.
+ */
+ virtual uint32_t RowCount() { return 0; }
+
+ /**
+ * Return the accessible for the cell at the given row and column indices.
+ */
+ virtual Accessible* CellAt(uint32_t aRowIdx, uint32_t aColIdx) { return nullptr; }
+
+ /**
+ * Return the index of the cell at the given row and column.
+ */
+ virtual int32_t CellIndexAt(uint32_t aRowIdx, uint32_t aColIdx)
+ { return ColCount() * aRowIdx + aColIdx; }
+
+ /**
+ * Return the column index of the cell with the given index.
+ */
+ virtual int32_t ColIndexAt(uint32_t aCellIdx)
+ { return aCellIdx % ColCount(); }
+
+ /**
+ * Return the row index of the cell with the given index.
+ */
+ virtual int32_t RowIndexAt(uint32_t aCellIdx)
+ { return aCellIdx / ColCount(); }
+
+ /**
+ * Get the row and column indices for the cell at the given index.
+ */
+ virtual void RowAndColIndicesAt(uint32_t aCellIdx, int32_t* aRowIdx,
+ int32_t* aColIdx)
+ {
+ uint32_t colCount = ColCount();
+ *aRowIdx = aCellIdx / colCount;
+ *aColIdx = aCellIdx % colCount;
+ }
+
+ /**
+ * Return the number of columns occupied by the cell at the given row and
+ * column indices.
+ */
+ virtual uint32_t ColExtentAt(uint32_t aRowIdx, uint32_t aColIdx) { return 1; }
+
+ /**
+ * Return the number of rows occupied by the cell at the given row and column
+ * indices.
+ */
+ virtual uint32_t RowExtentAt(uint32_t aRowIdx, uint32_t aColIdx) { return 1; }
+
+ /**
+ * Get the description of the given column.
+ */
+ virtual void ColDescription(uint32_t aColIdx, nsString& aDescription)
+ { aDescription.Truncate(); }
+
+ /**
+ * Get the description for the given row.
+ */
+ virtual void RowDescription(uint32_t aRowIdx, nsString& aDescription)
+ { aDescription.Truncate(); }
+
+ /**
+ * Return true if the given column is selected.
+ */
+ virtual bool IsColSelected(uint32_t aColIdx) { return false; }
+
+ /**
+ * Return true if the given row is selected.
+ */
+ virtual bool IsRowSelected(uint32_t aRowIdx) { return false; }
+
+ /**
+ * Return true if the given cell is selected.
+ */
+ virtual bool IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) { return false; }
+
+ /**
+ * Return the number of selected cells.
+ */
+ virtual uint32_t SelectedCellCount() { return 0; }
+
+ /**
+ * Return the number of selected columns.
+ */
+ virtual uint32_t SelectedColCount() { return 0; }
+
+ /**
+ * Return the number of selected rows.
+ */
+ virtual uint32_t SelectedRowCount() { return 0; }
+
+ /**
+ * Get the set of selected cells.
+ */
+ virtual void SelectedCells(nsTArray<Accessible*>* aCells) = 0;
+
+ /**
+ * Get the set of selected cell indices.
+ */
+ virtual void SelectedCellIndices(nsTArray<uint32_t>* aCells) = 0;
+
+ /**
+ * Get the set of selected column indices.
+ */
+ virtual void SelectedColIndices(nsTArray<uint32_t>* aCols) = 0;
+
+ /**
+ * Get the set of selected row indices.
+ */
+ virtual void SelectedRowIndices(nsTArray<uint32_t>* aRows) = 0;
+
+ /**
+ * Select the given column unselecting any other selected columns.
+ */
+ virtual void SelectCol(uint32_t aColIdx) {}
+
+ /**
+ * Select the given row unselecting all other previously selected rows.
+ */
+ virtual void SelectRow(uint32_t aRowIdx) {}
+
+ /**
+ * Unselect the given column leaving other selected columns selected.
+ */
+ virtual void UnselectCol(uint32_t aColIdx) {}
+
+ /**
+ * Unselect the given row leaving other selected rows selected.
+ */
+ virtual void UnselectRow(uint32_t aRowIdx) {}
+
+ /**
+ * Return true if the table is probably for layout.
+ */
+ virtual bool IsProbablyLayoutTable() { return false; }
+
+ /**
+ * Convert the table to an Accessible*.
+ */
+ virtual Accessible* AsAccessible() = 0;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/generic/TableCellAccessible.cpp b/accessible/generic/TableCellAccessible.cpp
new file mode 100644
index 000000000..3c840127c
--- /dev/null
+++ b/accessible/generic/TableCellAccessible.cpp
@@ -0,0 +1,67 @@
+/* -*- 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 "TableCellAccessible.h"
+
+#include "Accessible-inl.h"
+#include "TableAccessible.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+void
+TableCellAccessible::RowHeaderCells(nsTArray<Accessible*>* aCells)
+{
+ uint32_t rowIdx = RowIdx(), colIdx = ColIdx();
+ TableAccessible* table = Table();
+ if (!table)
+ return;
+
+ // Move to the left to find row header cells
+ for (uint32_t curColIdx = colIdx - 1; curColIdx < colIdx; curColIdx--) {
+ Accessible* cell = table->CellAt(rowIdx, curColIdx);
+ if (!cell)
+ continue;
+
+ // CellAt should always return a TableCellAccessible (XXX Bug 587529)
+ TableCellAccessible* tableCell = cell->AsTableCell();
+ NS_ASSERTION(tableCell, "cell should be a table cell!");
+ if (!tableCell)
+ continue;
+
+ // Avoid addding cells multiple times, if this cell spans more columns
+ // we'll get it later.
+ if (tableCell->ColIdx() == curColIdx && cell->Role() == roles::ROWHEADER)
+ aCells->AppendElement(cell);
+ }
+}
+
+void
+TableCellAccessible::ColHeaderCells(nsTArray<Accessible*>* aCells)
+{
+ uint32_t rowIdx = RowIdx(), colIdx = ColIdx();
+ TableAccessible* table = Table();
+ if (!table)
+ return;
+
+ // Move up to find column header cells
+ for (uint32_t curRowIdx = rowIdx - 1; curRowIdx < rowIdx; curRowIdx--) {
+ Accessible* cell = table->CellAt(curRowIdx, colIdx);
+ if (!cell)
+ continue;
+
+ // CellAt should always return a TableCellAccessible (XXX Bug 587529)
+ TableCellAccessible* tableCell = cell->AsTableCell();
+ NS_ASSERTION(tableCell, "cell should be a table cell!");
+ if (!tableCell)
+ continue;
+
+ // Avoid addding cells multiple times, if this cell spans more rows
+ // we'll get it later.
+ if (tableCell->RowIdx() == curRowIdx && cell->Role() == roles::COLUMNHEADER)
+ aCells->AppendElement(cell);
+ }
+}
diff --git a/accessible/generic/TableCellAccessible.h b/accessible/generic/TableCellAccessible.h
new file mode 100644
index 000000000..327552fe8
--- /dev/null
+++ b/accessible/generic/TableCellAccessible.h
@@ -0,0 +1,70 @@
+/* -*- 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_TableCellAccessible_h__
+#define mozilla_a11y_TableCellAccessible_h__
+
+#include "nsTArray.h"
+#include <stdint.h>
+
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+class TableAccessible;
+
+/**
+ * Abstract interface implemented by table cell accessibles.
+ */
+class TableCellAccessible
+{
+public:
+
+ /**
+ * Return the table this cell is in.
+ */
+ virtual TableAccessible* Table() const = 0;
+
+ /**
+ * Return the column of the table this cell is in.
+ */
+ virtual uint32_t ColIdx() const = 0;
+
+ /**
+ * Return the row of the table this cell is in.
+ */
+ virtual uint32_t RowIdx() const = 0;
+
+ /**
+ * Return the column extent of this cell.
+ */
+ virtual uint32_t ColExtent() const { return 1; }
+
+ /**
+ * Return the row extent of this cell.
+ */
+ virtual uint32_t RowExtent() const { return 1; }
+
+ /**
+ * Return the column header cells for this cell.
+ */
+ virtual void ColHeaderCells(nsTArray<Accessible*>* aCells);
+
+ /**
+ * Return the row header cells for this cell.
+ */
+ virtual void RowHeaderCells(nsTArray<Accessible*>* aCells);
+
+ /**
+ * Returns true if this cell is selected.
+ */
+ virtual bool Selected() = 0;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_TableCellAccessible_h__
diff --git a/accessible/generic/TextLeafAccessible.cpp b/accessible/generic/TextLeafAccessible.cpp
new file mode 100644
index 000000000..9808833e1
--- /dev/null
+++ b/accessible/generic/TextLeafAccessible.cpp
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "TextLeafAccessible.h"
+
+#include "nsAccUtils.h"
+#include "DocAccessible.h"
+#include "Role.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// TextLeafAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+TextLeafAccessible::
+ TextLeafAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ LinkableAccessible(aContent, aDoc)
+{
+ mType = eTextLeafType;
+ mGenericTypes |= eText;
+ mStateFlags |= eNoKidsFromDOM;
+}
+
+TextLeafAccessible::~TextLeafAccessible()
+{
+}
+
+role
+TextLeafAccessible::NativeRole()
+{
+ nsIFrame* frame = GetFrame();
+ if (frame && frame->IsGeneratedContentFrame())
+ return roles::STATICTEXT;
+
+ return roles::TEXT_LEAF;
+}
+
+void
+TextLeafAccessible::AppendTextTo(nsAString& aText, uint32_t aStartOffset,
+ uint32_t aLength)
+{
+ aText.Append(Substring(mText, aStartOffset, aLength));
+}
+
+ENameValueFlag
+TextLeafAccessible::Name(nsString& aName)
+{
+ // Text node, ARIA can't be used.
+ aName = mText;
+ return eNameOK;
+}
diff --git a/accessible/generic/TextLeafAccessible.h b/accessible/generic/TextLeafAccessible.h
new file mode 100644
index 000000000..555929fbf
--- /dev/null
+++ b/accessible/generic/TextLeafAccessible.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_TextLeafAccessible_h__
+#define mozilla_a11y_TextLeafAccessible_h__
+
+#include "BaseAccessibles.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Generic class used for text nodes.
+ */
+class TextLeafAccessible : public LinkableAccessible
+{
+public:
+ TextLeafAccessible(nsIContent* aContent, DocAccessible* aDoc);
+ virtual ~TextLeafAccessible();
+
+ // Accessible
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual void AppendTextTo(nsAString& aText, uint32_t aStartOffset = 0,
+ uint32_t aLength = UINT32_MAX) override;
+ virtual ENameValueFlag Name(nsString& aName) override;
+
+ // TextLeafAccessible
+ void SetText(const nsAString& aText) { mText = aText; }
+ const nsString& Text() const { return mText; }
+
+protected:
+ nsString mText;
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible downcast method
+
+inline TextLeafAccessible*
+Accessible::AsTextLeaf()
+{
+ return IsTextLeaf() ? static_cast<TextLeafAccessible*>(this) : nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/generic/moz.build b/accessible/generic/moz.build
new file mode 100644
index 000000000..a9e97acf0
--- /dev/null
+++ b/accessible/generic/moz.build
@@ -0,0 +1,70 @@
+# -*- 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 += [
+ 'Accessible.h',
+ 'DocAccessible.h',
+ 'HyperTextAccessible.h',
+]
+
+UNIFIED_SOURCES += [
+ 'Accessible.cpp',
+ 'ApplicationAccessible.cpp',
+ 'ARIAGridAccessible.cpp',
+ 'BaseAccessibles.cpp',
+ 'DocAccessible.cpp',
+ 'FormControlAccessible.cpp',
+ 'HyperTextAccessible.cpp',
+ 'ImageAccessible.cpp',
+ 'OuterDocAccessible.cpp',
+ 'RootAccessible.cpp',
+ 'TableCellAccessible.cpp',
+ 'TextLeafAccessible.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/accessible/base',
+ '/accessible/html',
+ '/accessible/xpcom',
+ '/accessible/xul',
+ '/dom/base',
+ '/layout/generic',
+ '/layout/xul',
+]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ LOCAL_INCLUDES += [
+ '/accessible/ipc/win',
+ ]
+else:
+ LOCAL_INCLUDES += [
+ '/accessible/ipc/other',
+ ]
+
+if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ LOCAL_INCLUDES += [
+ '/accessible/atk',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ LOCAL_INCLUDES += [
+ '/accessible/windows/ia2',
+ '/accessible/windows/msaa',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ LOCAL_INCLUDES += [
+ '/accessible/mac',
+ ]
+else:
+ LOCAL_INCLUDES += [
+ '/accessible/other',
+ ]
+
+FINAL_LIBRARY = 'xul'
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/accessible/html/HTMLCanvasAccessible.cpp b/accessible/html/HTMLCanvasAccessible.cpp
new file mode 100644
index 000000000..4cc748728
--- /dev/null
+++ b/accessible/html/HTMLCanvasAccessible.cpp
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "HTMLCanvasAccessible.h"
+
+#include "Role.h"
+
+using namespace mozilla::a11y;
+
+HTMLCanvasAccessible::
+ HTMLCanvasAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HyperTextAccessibleWrap(aContent, aDoc)
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(HTMLCanvasAccessible, HyperTextAccessible)
+
+role
+HTMLCanvasAccessible::NativeRole()
+{
+ return roles::CANVAS;
+}
diff --git a/accessible/html/HTMLCanvasAccessible.h b/accessible/html/HTMLCanvasAccessible.h
new file mode 100644
index 000000000..f07b84435
--- /dev/null
+++ b/accessible/html/HTMLCanvasAccessible.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_HTMLCanvasAccessible_h__
+#define mozilla_a11y_HTMLCanvasAccessible_h__
+
+#include "HyperTextAccessibleWrap.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * HTML canvas accessible (html:canvas).
+ */
+class HTMLCanvasAccessible : public HyperTextAccessibleWrap
+{
+public:
+ HTMLCanvasAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+
+protected:
+ virtual ~HTMLCanvasAccessible() { }
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/html/HTMLElementAccessibles.cpp b/accessible/html/HTMLElementAccessibles.cpp
new file mode 100644
index 000000000..39601151b
--- /dev/null
+++ b/accessible/html/HTMLElementAccessibles.cpp
@@ -0,0 +1,204 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "HTMLElementAccessibles.h"
+
+#include "DocAccessible.h"
+#include "nsAccUtils.h"
+#include "nsIPersistentProperties2.h"
+#include "nsTextEquivUtils.h"
+#include "Relation.h"
+#include "Role.h"
+#include "States.h"
+
+#include "mozilla/dom/HTMLLabelElement.h"
+#include "mozilla/dom/HTMLDetailsElement.h"
+#include "mozilla/dom/HTMLSummaryElement.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLHRAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+role
+HTMLHRAccessible::NativeRole()
+{
+ return roles::SEPARATOR;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLBRAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+role
+HTMLBRAccessible::NativeRole()
+{
+ return roles::WHITESPACE;
+}
+
+uint64_t
+HTMLBRAccessible::NativeState()
+{
+ return states::READONLY;
+}
+
+ENameValueFlag
+HTMLBRAccessible::NativeName(nsString& aName)
+{
+ aName = static_cast<char16_t>('\n'); // Newline char
+ return eNameOK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLLabelAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS_INHERITED0(HTMLLabelAccessible, HyperTextAccessible)
+
+ENameValueFlag
+HTMLLabelAccessible::NativeName(nsString& aName)
+{
+ nsTextEquivUtils::GetNameFromSubtree(this, aName);
+ return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
+}
+
+Relation
+HTMLLabelAccessible::RelationByType(RelationType aType)
+{
+ Relation rel = AccessibleWrap::RelationByType(aType);
+ if (aType == RelationType::LABEL_FOR) {
+ dom::HTMLLabelElement* label = dom::HTMLLabelElement::FromContent(mContent);
+ rel.AppendTarget(mDoc, label->GetControl());
+ }
+
+ return rel;
+}
+
+uint8_t
+HTMLLabelAccessible::ActionCount()
+{
+ return nsCoreUtils::IsLabelWithControl(mContent) ? 1 : 0;
+}
+
+void
+HTMLLabelAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ if (aIndex == 0) {
+ if (nsCoreUtils::IsLabelWithControl(mContent))
+ aName.AssignLiteral("click");
+ }
+}
+
+bool
+HTMLLabelAccessible::DoAction(uint8_t aIndex)
+{
+ if (aIndex != 0)
+ return false;
+
+ DoCommand();
+ return true;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// nsHTMLOuputAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS_INHERITED0(HTMLOutputAccessible, HyperTextAccessible)
+
+Relation
+HTMLOutputAccessible::RelationByType(RelationType aType)
+{
+ Relation rel = AccessibleWrap::RelationByType(aType);
+ if (aType == RelationType::CONTROLLED_BY)
+ rel.AppendIter(new IDRefsIterator(mDoc, mContent, nsGkAtoms::_for));
+
+ return rel;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLSummaryAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLSummaryAccessible::
+ HTMLSummaryAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HyperTextAccessibleWrap(aContent, aDoc)
+{
+ mGenericTypes |= eButton;
+}
+
+uint8_t
+HTMLSummaryAccessible::ActionCount()
+{
+ return 1;
+}
+
+void
+HTMLSummaryAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ if (aIndex != eAction_Click) {
+ return;
+ }
+
+ dom::HTMLSummaryElement* summary = dom::HTMLSummaryElement::FromContent(mContent);
+ if (!summary) {
+ return;
+ }
+
+ dom::HTMLDetailsElement* details = summary->GetDetails();
+ if (!details) {
+ return;
+ }
+
+ if (details->Open()) {
+ aName.AssignLiteral("collapse");
+ } else {
+ aName.AssignLiteral("expand");
+ }
+}
+
+bool
+HTMLSummaryAccessible::DoAction(uint8_t aIndex)
+{
+ if (aIndex != eAction_Click)
+ return false;
+
+ DoCommand();
+ return true;
+}
+
+uint64_t
+HTMLSummaryAccessible::NativeState()
+{
+ uint64_t state = HyperTextAccessibleWrap::NativeState();
+
+ dom::HTMLSummaryElement* summary = dom::HTMLSummaryElement::FromContent(mContent);
+ if (!summary) {
+ return state;
+ }
+
+ dom::HTMLDetailsElement* details = summary->GetDetails();
+ if (!details) {
+ return state;
+ }
+
+ if (details->Open()) {
+ state |= states::EXPANDED;
+ } else {
+ state |= states::COLLAPSED;
+ }
+
+ return state;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLSummaryAccessible: Widgets
+
+bool
+HTMLSummaryAccessible::IsWidget() const
+{
+ return true;
+}
diff --git a/accessible/html/HTMLElementAccessibles.h b/accessible/html/HTMLElementAccessibles.h
new file mode 100644
index 000000000..83abbe9e6
--- /dev/null
+++ b/accessible/html/HTMLElementAccessibles.h
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_HTMLElementAccessibles_h__
+#define mozilla_a11y_HTMLElementAccessibles_h__
+
+#include "BaseAccessibles.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Used for HTML hr element.
+ */
+class HTMLHRAccessible : public LeafAccessible
+{
+public:
+
+ HTMLHRAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ LeafAccessible(aContent, aDoc) {}
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+};
+
+/**
+ * Used for HTML br element.
+ */
+class HTMLBRAccessible : public LeafAccessible
+{
+public:
+ HTMLBRAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ LeafAccessible(aContent, aDoc)
+ {
+ mType = eHTMLBRType;
+ mGenericTypes |= eText;
+ }
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+
+protected:
+ // Accessible
+ virtual ENameValueFlag NativeName(nsString& aName) override;
+};
+
+/**
+ * Used for HTML label element.
+ */
+class HTMLLabelAccessible : public HyperTextAccessibleWrap
+{
+public:
+
+ HTMLLabelAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HyperTextAccessibleWrap(aContent, aDoc) {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual Relation RelationByType(RelationType aType) override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+
+protected:
+ virtual ~HTMLLabelAccessible() {}
+ virtual ENameValueFlag NativeName(nsString& aName) override;
+};
+
+/**
+ * Used for HTML output element.
+ */
+class HTMLOutputAccessible : public HyperTextAccessibleWrap
+{
+public:
+
+ HTMLOutputAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HyperTextAccessibleWrap(aContent, aDoc) {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual Relation RelationByType(RelationType aType) override;
+
+protected:
+ virtual ~HTMLOutputAccessible() {}
+};
+
+/**
+ * Accessible for the HTML summary element.
+ */
+class HTMLSummaryAccessible : public HyperTextAccessibleWrap
+{
+
+public:
+ enum { eAction_Click = 0 };
+
+ HTMLSummaryAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual uint64_t NativeState() override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+
+ // Widgets
+ virtual bool IsWidget() const override;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/html/HTMLFormControlAccessible.cpp b/accessible/html/HTMLFormControlAccessible.cpp
new file mode 100644
index 000000000..a7b3874a1
--- /dev/null
+++ b/accessible/html/HTMLFormControlAccessible.cpp
@@ -0,0 +1,841 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "HTMLFormControlAccessible.h"
+
+#include "Accessible-inl.h"
+#include "nsAccUtils.h"
+#include "nsEventShell.h"
+#include "nsTextEquivUtils.h"
+#include "Relation.h"
+#include "Role.h"
+#include "States.h"
+
+#include "nsContentList.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "nsIDOMNSEditableElement.h"
+#include "nsIDOMHTMLTextAreaElement.h"
+#include "nsIEditor.h"
+#include "nsIFormControl.h"
+#include "nsIPersistentProperties2.h"
+#include "nsISelectionController.h"
+#include "nsIServiceManager.h"
+#include "nsITextControlFrame.h"
+#include "nsNameSpaceManager.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+#include "mozilla/EventStates.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Preferences.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLCheckboxAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+role
+HTMLCheckboxAccessible::NativeRole()
+{
+ return roles::CHECKBUTTON;
+}
+
+uint8_t
+HTMLCheckboxAccessible::ActionCount()
+{
+ return 1;
+}
+
+void
+HTMLCheckboxAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ if (aIndex == eAction_Click) { // 0 is the magic value for default action
+ uint64_t state = NativeState();
+ if (state & states::CHECKED)
+ aName.AssignLiteral("uncheck");
+ else if (state & states::MIXED)
+ aName.AssignLiteral("cycle");
+ else
+ aName.AssignLiteral("check");
+ }
+}
+
+bool
+HTMLCheckboxAccessible::DoAction(uint8_t aIndex)
+{
+ if (aIndex != 0)
+ return false;
+
+ DoCommand();
+ return true;
+}
+
+uint64_t
+HTMLCheckboxAccessible::NativeState()
+{
+ uint64_t state = LeafAccessible::NativeState();
+
+ state |= states::CHECKABLE;
+ HTMLInputElement* input = HTMLInputElement::FromContent(mContent);
+ if (!input)
+ return state;
+
+ if (input->Indeterminate())
+ return state | states::MIXED;
+
+ if (input->Checked())
+ return state | states::CHECKED;
+
+ return state;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLCheckboxAccessible: Widgets
+
+bool
+HTMLCheckboxAccessible::IsWidget() const
+{
+ return true;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLRadioButtonAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+uint64_t
+HTMLRadioButtonAccessible::NativeState()
+{
+ uint64_t state = AccessibleWrap::NativeState();
+
+ state |= states::CHECKABLE;
+
+ HTMLInputElement* input = HTMLInputElement::FromContent(mContent);
+ if (input && input->Checked())
+ state |= states::CHECKED;
+
+ return state;
+}
+
+void
+HTMLRadioButtonAccessible::GetPositionAndSizeInternal(int32_t* aPosInSet,
+ int32_t* aSetSize)
+{
+ int32_t namespaceId = mContent->NodeInfo()->NamespaceID();
+ nsAutoString tagName;
+ mContent->NodeInfo()->GetName(tagName);
+
+ nsAutoString type;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type);
+ nsAutoString name;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+
+ RefPtr<nsContentList> inputElms;
+
+ nsCOMPtr<nsIFormControl> formControlNode(do_QueryInterface(mContent));
+ dom::Element* formElm = formControlNode->GetFormElement();
+ if (formElm)
+ inputElms = NS_GetContentList(formElm, namespaceId, tagName);
+ else
+ inputElms = NS_GetContentList(mContent->OwnerDoc(), namespaceId, tagName);
+ NS_ENSURE_TRUE_VOID(inputElms);
+
+ uint32_t inputCount = inputElms->Length(false);
+
+ // Compute posinset and setsize.
+ int32_t indexOf = 0;
+ int32_t count = 0;
+
+ for (uint32_t index = 0; index < inputCount; index++) {
+ nsIContent* inputElm = inputElms->Item(index, false);
+ if (inputElm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ type, eCaseMatters) &&
+ inputElm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
+ name, eCaseMatters) && mDoc->HasAccessible(inputElm)) {
+ count++;
+ if (inputElm == mContent)
+ indexOf = count;
+ }
+ }
+
+ *aPosInSet = indexOf;
+ *aSetSize = count;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLButtonAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLButtonAccessible::
+ HTMLButtonAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HyperTextAccessibleWrap(aContent, aDoc)
+{
+ mGenericTypes |= eButton;
+}
+
+uint8_t
+HTMLButtonAccessible::ActionCount()
+{
+ return 1;
+}
+
+void
+HTMLButtonAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ if (aIndex == eAction_Click)
+ aName.AssignLiteral("press");
+}
+
+bool
+HTMLButtonAccessible::DoAction(uint8_t aIndex)
+{
+ if (aIndex != eAction_Click)
+ return false;
+
+ DoCommand();
+ return true;
+}
+
+uint64_t
+HTMLButtonAccessible::State()
+{
+ uint64_t state = HyperTextAccessibleWrap::State();
+ if (state == states::DEFUNCT)
+ return state;
+
+ // Inherit states from input@type="file" suitable for the button. Note,
+ // no special processing for unavailable state since inheritance is supplied
+ // other code paths.
+ if (mParent && mParent->IsHTMLFileInput()) {
+ uint64_t parentState = mParent->State();
+ state |= parentState & (states::BUSY | states::REQUIRED |
+ states::HASPOPUP | states::INVALID);
+ }
+
+ return state;
+}
+
+uint64_t
+HTMLButtonAccessible::NativeState()
+{
+ uint64_t state = HyperTextAccessibleWrap::NativeState();
+
+ EventStates elmState = mContent->AsElement()->State();
+ if (elmState.HasState(NS_EVENT_STATE_DEFAULT))
+ state |= states::DEFAULT;
+
+ return state;
+}
+
+role
+HTMLButtonAccessible::NativeRole()
+{
+ return roles::PUSHBUTTON;
+}
+
+ENameValueFlag
+HTMLButtonAccessible::NativeName(nsString& aName)
+{
+ // No need to check @value attribute for buttons since this attribute results
+ // in native anonymous text node and the name is calculated from subtree.
+ // The same magic works for @alt and @value attributes in case of type="image"
+ // element that has no valid @src (note if input@type="image" has an image
+ // then neither @alt nor @value attributes are used to generate a visual label
+ // and thus we need to obtain the accessible name directly from attribute
+ // value). Also the same algorithm works in case of default labels for
+ // type="submit"/"reset"/"image" elements.
+
+ ENameValueFlag nameFlag = Accessible::NativeName(aName);
+ if (!aName.IsEmpty() || !mContent->IsHTMLElement(nsGkAtoms::input) ||
+ !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::image, eCaseMatters))
+ return nameFlag;
+
+ if (!mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::alt, aName))
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, aName);
+
+ aName.CompressWhitespace();
+ return eNameOK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLButtonAccessible: Widgets
+
+bool
+HTMLButtonAccessible::IsWidget() const
+{
+ return true;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTextFieldAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLTextFieldAccessible::
+ HTMLTextFieldAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HyperTextAccessibleWrap(aContent, aDoc)
+{
+ mType = eHTMLTextFieldType;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(HTMLTextFieldAccessible,
+ HyperTextAccessible)
+
+role
+HTMLTextFieldAccessible::NativeRole()
+{
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::password, eIgnoreCase)) {
+ return roles::PASSWORD_TEXT;
+ }
+
+ return roles::ENTRY;
+}
+
+already_AddRefed<nsIPersistentProperties>
+HTMLTextFieldAccessible::NativeAttributes()
+{
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ HyperTextAccessibleWrap::NativeAttributes();
+
+ // Expose type for text input elements as it gives some useful context,
+ // especially for mobile.
+ nsAutoString type;
+ if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type)) {
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::textInputType, type);
+ if (!ARIARoleMap() && type.EqualsLiteral("search")) {
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
+ NS_LITERAL_STRING("searchbox"));
+ }
+ }
+
+ return attributes.forget();
+}
+
+ENameValueFlag
+HTMLTextFieldAccessible::NativeName(nsString& aName)
+{
+ ENameValueFlag nameFlag = Accessible::NativeName(aName);
+ if (!aName.IsEmpty())
+ return nameFlag;
+
+ // If part of compound of XUL widget then grab a name from XUL widget element.
+ nsIContent* widgetElm = XULWidgetElm();
+ if (widgetElm)
+ XULElmName(mDoc, widgetElm, aName);
+
+ if (!aName.IsEmpty())
+ return eNameOK;
+
+ // text inputs and textareas might have useful placeholder text
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, aName);
+ return eNameOK;
+}
+
+void
+HTMLTextFieldAccessible::Value(nsString& aValue)
+{
+ aValue.Truncate();
+ if (NativeState() & states::PROTECTED) // Don't return password text!
+ return;
+
+ nsCOMPtr<nsIDOMHTMLTextAreaElement> textArea(do_QueryInterface(mContent));
+ if (textArea) {
+ textArea->GetValue(aValue);
+ return;
+ }
+
+ HTMLInputElement* input = HTMLInputElement::FromContent(mContent);
+ if (input)
+ input->GetValue(aValue);
+}
+
+void
+HTMLTextFieldAccessible::ApplyARIAState(uint64_t* aState) const
+{
+ HyperTextAccessibleWrap::ApplyARIAState(aState);
+ aria::MapToState(aria::eARIAAutoComplete, mContent->AsElement(), aState);
+
+ // If part of compound of XUL widget then pick up ARIA stuff from XUL widget
+ // element.
+ nsIContent* widgetElm = XULWidgetElm();
+ if (widgetElm)
+ aria::MapToState(aria::eARIAAutoComplete, widgetElm->AsElement(), aState);
+}
+
+uint64_t
+HTMLTextFieldAccessible::NativeState()
+{
+ uint64_t state = HyperTextAccessibleWrap::NativeState();
+
+ // Text fields are always editable, even if they are also read only or
+ // disabled.
+ state |= states::EDITABLE;
+
+ // can be focusable, focused, protected. readonly, unavailable, selected
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::password, eIgnoreCase)) {
+ state |= states::PROTECTED;
+ }
+
+ if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::readonly)) {
+ state |= states::READONLY;
+ }
+
+ // Is it an <input> or a <textarea> ?
+ HTMLInputElement* input = HTMLInputElement::FromContent(mContent);
+ state |= input && input->IsSingleLineTextControl() ?
+ states::SINGLE_LINE : states::MULTI_LINE;
+
+ if (state & (states::PROTECTED | states::MULTI_LINE | states::READONLY |
+ states::UNAVAILABLE))
+ return state;
+
+ // Expose autocomplete states if this input is part of autocomplete widget.
+ Accessible* widget = ContainerWidget();
+ if (widget && widget-IsAutoComplete()) {
+ state |= states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION;
+ return state;
+ }
+
+ // Expose autocomplete state if it has associated autocomplete list.
+ if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::list))
+ return state | states::SUPPORTS_AUTOCOMPLETION | states::HASPOPUP;
+
+ // Ordinal XUL textboxes don't support autocomplete.
+ if (!XULWidgetElm() && Preferences::GetBool("browser.formfill.enable")) {
+ // Check to see if autocompletion is allowed on this input. We don't expose
+ // it for password fields even though the entire password can be remembered
+ // for a page if the user asks it to be. However, the kind of autocomplete
+ // we're talking here is based on what the user types, where a popup of
+ // possible choices comes up.
+ nsAutoString autocomplete;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete,
+ autocomplete);
+
+ if (!autocomplete.LowerCaseEqualsLiteral("off")) {
+ nsIContent* formContent = input->GetFormElement();
+ if (formContent) {
+ formContent->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::autocomplete, autocomplete);
+ }
+
+ if (!formContent || !autocomplete.LowerCaseEqualsLiteral("off"))
+ state |= states::SUPPORTS_AUTOCOMPLETION;
+ }
+ }
+
+ return state;
+}
+
+uint8_t
+HTMLTextFieldAccessible::ActionCount()
+{
+ return 1;
+}
+
+void
+HTMLTextFieldAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ if (aIndex == eAction_Click)
+ aName.AssignLiteral("activate");
+}
+
+bool
+HTMLTextFieldAccessible::DoAction(uint8_t aIndex)
+{
+ if (aIndex != 0)
+ return false;
+
+ TakeFocus();
+ return true;
+}
+
+already_AddRefed<nsIEditor>
+HTMLTextFieldAccessible::GetEditor() const
+{
+ nsCOMPtr<nsIDOMNSEditableElement> editableElt(do_QueryInterface(mContent));
+ if (!editableElt)
+ return nullptr;
+
+ // nsGenericHTMLElement::GetEditor has a security check.
+ // Make sure we're not restricted by the permissions of
+ // whatever script is currently running.
+ mozilla::dom::AutoNoJSAPI nojsapi;
+
+ nsCOMPtr<nsIEditor> editor;
+ editableElt->GetEditor(getter_AddRefs(editor));
+
+ return editor.forget();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTextFieldAccessible: Widgets
+
+bool
+HTMLTextFieldAccessible::IsWidget() const
+{
+ return true;
+}
+
+Accessible*
+HTMLTextFieldAccessible::ContainerWidget() const
+{
+ if (!mParent || mParent->Role() != roles::AUTOCOMPLETE) {
+ return nullptr;
+ }
+ return mParent;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLFileInputAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLFileInputAccessible::
+HTMLFileInputAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HyperTextAccessibleWrap(aContent, aDoc)
+{
+ mType = eHTMLFileInputType;
+}
+
+role
+HTMLFileInputAccessible::NativeRole()
+{
+ // JAWS wants a text container, others don't mind. No specific role in
+ // AT APIs.
+ return roles::TEXT_CONTAINER;
+}
+
+nsresult
+HTMLFileInputAccessible::HandleAccEvent(AccEvent* aEvent)
+{
+ nsresult rv = HyperTextAccessibleWrap::HandleAccEvent(aEvent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Redirect state change events for inherited states to child controls. Note,
+ // unavailable state is not redirected. That's a standard for unavailable
+ // state handling.
+ AccStateChangeEvent* event = downcast_accEvent(aEvent);
+ if (event &&
+ (event->GetState() == states::BUSY ||
+ event->GetState() == states::REQUIRED ||
+ event->GetState() == states::HASPOPUP ||
+ event->GetState() == states::INVALID)) {
+ Accessible* button = GetChildAt(0);
+ if (button && button->Role() == roles::PUSHBUTTON) {
+ RefPtr<AccStateChangeEvent> childEvent =
+ new AccStateChangeEvent(button, event->GetState(),
+ event->IsStateEnabled(), event->FromUserInput());
+ nsEventShell::FireEvent(childEvent);
+ }
+ }
+
+ return NS_OK;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLSpinnerAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+role
+HTMLSpinnerAccessible::NativeRole()
+{
+ return roles::SPINBUTTON;
+}
+
+void
+HTMLSpinnerAccessible::Value(nsString& aValue)
+{
+ AccessibleWrap::Value(aValue);
+ if (!aValue.IsEmpty())
+ return;
+
+ HTMLInputElement::FromContent(mContent)->GetValue(aValue);
+}
+
+double
+HTMLSpinnerAccessible::MaxValue() const
+{
+ double value = AccessibleWrap::MaxValue();
+ if (!IsNaN(value))
+ return value;
+
+ return HTMLInputElement::FromContent(mContent)->GetMaximum().toDouble();
+}
+
+
+double
+HTMLSpinnerAccessible::MinValue() const
+{
+ double value = AccessibleWrap::MinValue();
+ if (!IsNaN(value))
+ return value;
+
+ return HTMLInputElement::FromContent(mContent)->GetMinimum().toDouble();
+}
+
+double
+HTMLSpinnerAccessible::Step() const
+{
+ double value = AccessibleWrap::Step();
+ if (!IsNaN(value))
+ return value;
+
+ return HTMLInputElement::FromContent(mContent)->GetStep().toDouble();
+}
+
+double
+HTMLSpinnerAccessible::CurValue() const
+{
+ double value = AccessibleWrap::CurValue();
+ if (!IsNaN(value))
+ return value;
+
+ return HTMLInputElement::FromContent(mContent)->GetValueAsDecimal().toDouble();
+}
+
+bool
+HTMLSpinnerAccessible::SetCurValue(double aValue)
+{
+ ErrorResult er;
+ HTMLInputElement::FromContent(mContent)->SetValueAsNumber(aValue, er);
+ return !er.Failed();
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLRangeAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+role
+HTMLRangeAccessible::NativeRole()
+{
+ return roles::SLIDER;
+}
+
+bool
+HTMLRangeAccessible::IsWidget() const
+{
+ return true;
+}
+
+void
+HTMLRangeAccessible::Value(nsString& aValue)
+{
+ LeafAccessible::Value(aValue);
+ if (!aValue.IsEmpty())
+ return;
+
+ HTMLInputElement::FromContent(mContent)->GetValue(aValue);
+}
+
+double
+HTMLRangeAccessible::MaxValue() const
+{
+ double value = LeafAccessible::MaxValue();
+ if (!IsNaN(value))
+ return value;
+
+ return HTMLInputElement::FromContent(mContent)->GetMaximum().toDouble();
+}
+
+double
+HTMLRangeAccessible::MinValue() const
+{
+ double value = LeafAccessible::MinValue();
+ if (!IsNaN(value))
+ return value;
+
+ return HTMLInputElement::FromContent(mContent)->GetMinimum().toDouble();
+}
+
+double
+HTMLRangeAccessible::Step() const
+{
+ double value = LeafAccessible::Step();
+ if (!IsNaN(value))
+ return value;
+
+ return HTMLInputElement::FromContent(mContent)->GetStep().toDouble();
+}
+
+double
+HTMLRangeAccessible::CurValue() const
+{
+ double value = LeafAccessible::CurValue();
+ if (!IsNaN(value))
+ return value;
+
+ return HTMLInputElement::FromContent(mContent)->GetValueAsDecimal().toDouble();
+}
+
+bool
+HTMLRangeAccessible::SetCurValue(double aValue)
+{
+ ErrorResult er;
+ HTMLInputElement::FromContent(mContent)->SetValueAsNumber(aValue, er);
+ return !er.Failed();
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLGroupboxAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLGroupboxAccessible::
+ HTMLGroupboxAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HyperTextAccessibleWrap(aContent, aDoc)
+{
+}
+
+role
+HTMLGroupboxAccessible::NativeRole()
+{
+ return roles::GROUPING;
+}
+
+nsIContent*
+HTMLGroupboxAccessible::GetLegend() const
+{
+ for (nsIContent* legendContent = mContent->GetFirstChild(); legendContent;
+ legendContent = legendContent->GetNextSibling()) {
+ if (legendContent->NodeInfo()->Equals(nsGkAtoms::legend,
+ mContent->GetNameSpaceID())) {
+ // Either XHTML namespace or no namespace
+ return legendContent;
+ }
+ }
+
+ return nullptr;
+}
+
+ENameValueFlag
+HTMLGroupboxAccessible::NativeName(nsString& aName)
+{
+ ENameValueFlag nameFlag = Accessible::NativeName(aName);
+ if (!aName.IsEmpty())
+ return nameFlag;
+
+ nsIContent* legendContent = GetLegend();
+ if (legendContent)
+ nsTextEquivUtils::AppendTextEquivFromContent(this, legendContent, &aName);
+
+ return eNameOK;
+}
+
+Relation
+HTMLGroupboxAccessible::RelationByType(RelationType aType)
+{
+ Relation rel = HyperTextAccessibleWrap::RelationByType(aType);
+ // No override for label, so use <legend> for this <fieldset>
+ if (aType == RelationType::LABELLED_BY)
+ rel.AppendTarget(mDoc, GetLegend());
+
+ return rel;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLLegendAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLLegendAccessible::
+ HTMLLegendAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HyperTextAccessibleWrap(aContent, aDoc)
+{
+}
+
+Relation
+HTMLLegendAccessible::RelationByType(RelationType aType)
+{
+ Relation rel = HyperTextAccessibleWrap::RelationByType(aType);
+ if (aType != RelationType::LABEL_FOR)
+ return rel;
+
+ Accessible* groupbox = Parent();
+ if (groupbox && groupbox->Role() == roles::GROUPING)
+ rel.AppendTarget(groupbox);
+
+ return rel;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLFigureAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLFigureAccessible::
+ HTMLFigureAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HyperTextAccessibleWrap(aContent, aDoc)
+{
+}
+
+ENameValueFlag
+HTMLFigureAccessible::NativeName(nsString& aName)
+{
+ ENameValueFlag nameFlag = HyperTextAccessibleWrap::NativeName(aName);
+ if (!aName.IsEmpty())
+ return nameFlag;
+
+ nsIContent* captionContent = Caption();
+ if (captionContent)
+ nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent, &aName);
+
+ return eNameOK;
+}
+
+Relation
+HTMLFigureAccessible::RelationByType(RelationType aType)
+{
+ Relation rel = HyperTextAccessibleWrap::RelationByType(aType);
+ if (aType == RelationType::LABELLED_BY)
+ rel.AppendTarget(mDoc, Caption());
+
+ return rel;
+}
+
+nsIContent*
+HTMLFigureAccessible::Caption() const
+{
+ for (nsIContent* childContent = mContent->GetFirstChild(); childContent;
+ childContent = childContent->GetNextSibling()) {
+ if (childContent->NodeInfo()->Equals(nsGkAtoms::figcaption,
+ mContent->GetNameSpaceID())) {
+ return childContent;
+ }
+ }
+
+ return nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLFigcaptionAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLFigcaptionAccessible::
+ HTMLFigcaptionAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HyperTextAccessibleWrap(aContent, aDoc)
+{
+}
+
+Relation
+HTMLFigcaptionAccessible::RelationByType(RelationType aType)
+{
+ Relation rel = HyperTextAccessibleWrap::RelationByType(aType);
+ if (aType != RelationType::LABEL_FOR)
+ return rel;
+
+ Accessible* figure = Parent();
+ if (figure &&
+ figure->GetContent()->NodeInfo()->Equals(nsGkAtoms::figure,
+ mContent->GetNameSpaceID())) {
+ rel.AppendTarget(figure);
+ }
+
+ return rel;
+}
diff --git a/accessible/html/HTMLFormControlAccessible.h b/accessible/html/HTMLFormControlAccessible.h
new file mode 100644
index 000000000..c2f30d53a
--- /dev/null
+++ b/accessible/html/HTMLFormControlAccessible.h
@@ -0,0 +1,284 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_HTMLFormControlAccessible_H_
+#define MOZILLA_A11Y_HTMLFormControlAccessible_H_
+
+#include "FormControlAccessible.h"
+#include "HyperTextAccessibleWrap.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Accessible for HTML progress element.
+ */
+typedef ProgressMeterAccessible<1> HTMLProgressMeterAccessible;
+
+/**
+ * Accessible for HTML input@type="checkbox".
+ */
+class HTMLCheckboxAccessible : public LeafAccessible
+{
+
+public:
+ enum { eAction_Click = 0 };
+
+ HTMLCheckboxAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ LeafAccessible(aContent, aDoc)
+ {
+ // Ignore "CheckboxStateChange" DOM event in lieu of document observer
+ // state change notification.
+ mStateFlags |= eIgnoreDOMUIEvent;
+ }
+
+ // Accessible
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+
+ // Widgets
+ virtual bool IsWidget() const override;
+};
+
+
+/**
+ * Accessible for HTML input@type="radio" element.
+ */
+class HTMLRadioButtonAccessible : public RadioButtonAccessible
+{
+
+public:
+ HTMLRadioButtonAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ RadioButtonAccessible(aContent, aDoc)
+ {
+ // Ignore "RadioStateChange" DOM event in lieu of document observer
+ // state change notification.
+ mStateFlags |= eIgnoreDOMUIEvent;
+ }
+
+ // Accessible
+ virtual uint64_t NativeState() override;
+ virtual void GetPositionAndSizeInternal(int32_t *aPosInSet,
+ int32_t *aSetSize) override;
+};
+
+
+/**
+ * Accessible for HTML input@type="button", @type="submit", @type="image"
+ * and HTML button elements.
+ */
+class HTMLButtonAccessible : public HyperTextAccessibleWrap
+{
+
+public:
+ enum { eAction_Click = 0 };
+
+ HTMLButtonAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual uint64_t State() override;
+ virtual uint64_t NativeState() override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+
+ // Widgets
+ virtual bool IsWidget() const override;
+
+protected:
+ // Accessible
+ virtual ENameValueFlag NativeName(nsString& aName) override;
+};
+
+
+/**
+ * Accessible for HTML input@type="text", input@type="password", textarea and
+ * other HTML text controls.
+ */
+class HTMLTextFieldAccessible final : public HyperTextAccessibleWrap
+{
+
+public:
+ enum { eAction_Click = 0 };
+
+ HTMLTextFieldAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // HyperTextAccessible
+ virtual already_AddRefed<nsIEditor> GetEditor() const override;
+
+ // Accessible
+ virtual void Value(nsString& aValue) override;
+ virtual void ApplyARIAState(uint64_t* aState) const override;
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+ virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+
+ // Widgets
+ virtual bool IsWidget() const override;
+ virtual Accessible* ContainerWidget() const override;
+
+protected:
+ virtual ~HTMLTextFieldAccessible() {}
+
+ // Accessible
+ virtual ENameValueFlag NativeName(nsString& aName) override;
+
+ /**
+ * Return a XUL widget element this input is part of.
+ */
+ nsIContent* XULWidgetElm() const { return mContent->GetBindingParent(); }
+};
+
+
+/**
+ * Accessible for input@type="file" element.
+ */
+class HTMLFileInputAccessible : public HyperTextAccessibleWrap
+{
+public:
+ HTMLFileInputAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual nsresult HandleAccEvent(AccEvent* aAccEvent) override;
+};
+
+
+/**
+ * Used for HTML input@type="number".
+ */
+class HTMLSpinnerAccessible : public AccessibleWrap
+{
+public:
+ HTMLSpinnerAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+ {
+ mStateFlags |= eHasNumericValue;
+}
+
+ // Accessible
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual void Value(nsString& aValue) override;
+
+ virtual double MaxValue() const override;
+ virtual double MinValue() const override;
+ virtual double CurValue() const override;
+ virtual double Step() const override;
+ virtual bool SetCurValue(double aValue) override;
+};
+
+
+/**
+ * Used for input@type="range" element.
+ */
+class HTMLRangeAccessible : public LeafAccessible
+{
+public:
+ HTMLRangeAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ LeafAccessible(aContent, aDoc)
+ {
+ mStateFlags |= eHasNumericValue;
+ }
+
+ // Accessible
+ virtual void Value(nsString& aValue) override;
+ virtual mozilla::a11y::role NativeRole() override;
+
+ // Value
+ virtual double MaxValue() const override;
+ virtual double MinValue() const override;
+ virtual double CurValue() const override;
+ virtual double Step() const override;
+ virtual bool SetCurValue(double aValue) override;
+
+ // Widgets
+ virtual bool IsWidget() const override;
+};
+
+
+/**
+ * Accessible for HTML fieldset element.
+ */
+class HTMLGroupboxAccessible : public HyperTextAccessibleWrap
+{
+public:
+ HTMLGroupboxAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual Relation RelationByType(RelationType aType) override;
+
+protected:
+ // Accessible
+ virtual ENameValueFlag NativeName(nsString& aName) override;
+
+ // HTMLGroupboxAccessible
+ nsIContent* GetLegend() const;
+};
+
+
+/**
+ * Accessible for HTML legend element.
+ */
+class HTMLLegendAccessible : public HyperTextAccessibleWrap
+{
+public:
+ HTMLLegendAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual Relation RelationByType(RelationType aType) override;
+};
+
+/**
+ * Accessible for HTML5 figure element.
+ */
+class HTMLFigureAccessible : public HyperTextAccessibleWrap
+{
+public:
+ HTMLFigureAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual Relation RelationByType(RelationType aType) override;
+
+protected:
+ // Accessible
+ virtual ENameValueFlag NativeName(nsString& aName) override;
+
+ // HTMLLegendAccessible
+ nsIContent* Caption() const;
+};
+
+
+/**
+ * Accessible for HTML5 figcaption element.
+ */
+class HTMLFigcaptionAccessible : public HyperTextAccessibleWrap
+{
+public:
+ HTMLFigcaptionAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual Relation RelationByType(RelationType aType) override;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/html/HTMLImageMapAccessible.cpp b/accessible/html/HTMLImageMapAccessible.cpp
new file mode 100644
index 000000000..d6acbeba6
--- /dev/null
+++ b/accessible/html/HTMLImageMapAccessible.cpp
@@ -0,0 +1,228 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "HTMLImageMapAccessible.h"
+
+#include "ARIAMap.h"
+#include "nsAccUtils.h"
+#include "DocAccessible-inl.h"
+#include "Role.h"
+
+#include "nsIDOMHTMLCollection.h"
+#include "nsIServiceManager.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMHTMLAreaElement.h"
+#include "nsIFrame.h"
+#include "nsImageFrame.h"
+#include "nsImageMap.h"
+#include "nsIURI.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLImageMapAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLImageMapAccessible::
+ HTMLImageMapAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ ImageAccessibleWrap(aContent, aDoc)
+{
+ mType = eImageMapType;
+
+ UpdateChildAreas(false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLImageMapAccessible: nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED0(HTMLImageMapAccessible, ImageAccessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLImageMapAccessible: Accessible public
+
+role
+HTMLImageMapAccessible::NativeRole()
+{
+ return roles::IMAGE_MAP;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLImageMapAccessible: HyperLinkAccessible
+
+uint32_t
+HTMLImageMapAccessible::AnchorCount()
+{
+ return ChildCount();
+}
+
+Accessible*
+HTMLImageMapAccessible::AnchorAt(uint32_t aAnchorIndex)
+{
+ return GetChildAt(aAnchorIndex);
+}
+
+already_AddRefed<nsIURI>
+HTMLImageMapAccessible::AnchorURIAt(uint32_t aAnchorIndex)
+{
+ Accessible* area = GetChildAt(aAnchorIndex);
+ if (!area)
+ return nullptr;
+
+ nsIContent* linkContent = area->GetContent();
+ return linkContent ? linkContent->GetHrefURI() : nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLImageMapAccessible: public
+
+void
+HTMLImageMapAccessible::UpdateChildAreas(bool aDoFireEvents)
+{
+ nsImageFrame* imageFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+
+ // If image map is not initialized yet then we trigger one time more later.
+ nsImageMap* imageMapObj = imageFrame->GetExistingImageMap();
+ if (!imageMapObj)
+ return;
+
+ TreeMutation mt(this, TreeMutation::kNoEvents & !aDoFireEvents);
+
+ // Remove areas that are not a valid part of the image map anymore.
+ for (int32_t childIdx = mChildren.Length() - 1; childIdx >= 0; childIdx--) {
+ Accessible* area = mChildren.ElementAt(childIdx);
+ if (area->GetContent()->GetPrimaryFrame())
+ continue;
+
+ mt.BeforeRemoval(area);
+ RemoveChild(area);
+ }
+
+ // Insert new areas into the tree.
+ uint32_t areaElmCount = imageMapObj->AreaCount();
+ for (uint32_t idx = 0; idx < areaElmCount; idx++) {
+ nsIContent* areaContent = imageMapObj->GetAreaAt(idx);
+ Accessible* area = mChildren.SafeElementAt(idx);
+ if (!area || area->GetContent() != areaContent) {
+ RefPtr<Accessible> area = new HTMLAreaAccessible(areaContent, mDoc);
+ mDoc->BindToDocument(area, aria::GetRoleMap(areaContent->AsElement()));
+
+ if (!InsertChildAt(idx, area)) {
+ mDoc->UnbindFromDocument(area);
+ break;
+ }
+
+ mt.AfterInsertion(area);
+ }
+ }
+
+ mt.Done();
+}
+
+Accessible*
+HTMLImageMapAccessible::GetChildAccessibleFor(const nsINode* aNode) const
+{
+ uint32_t length = mChildren.Length();
+ for (uint32_t i = 0; i < length; i++) {
+ Accessible* area = mChildren[i];
+ if (area->GetContent() == aNode)
+ return area;
+ }
+
+ return nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLAreaAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLAreaAccessible::
+ HTMLAreaAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HTMLLinkAccessible(aContent, aDoc)
+{
+ // Make HTML area DOM element not accessible. HTML image map accessible
+ // manages its tree itself.
+ mStateFlags |= eNotNodeMapEntry;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLAreaAccessible: Accessible
+
+ENameValueFlag
+HTMLAreaAccessible::NativeName(nsString& aName)
+{
+ ENameValueFlag nameFlag = Accessible::NativeName(aName);
+ if (!aName.IsEmpty())
+ return nameFlag;
+
+ if (!mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::alt, aName))
+ Value(aName);
+
+ return eNameOK;
+}
+
+void
+HTMLAreaAccessible::Description(nsString& aDescription)
+{
+ aDescription.Truncate();
+
+ // Still to do - follow IE's standard here
+ nsCOMPtr<nsIDOMHTMLAreaElement> area(do_QueryInterface(mContent));
+ if (area)
+ area->GetShape(aDescription);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLAreaAccessible: Accessible public
+
+Accessible*
+HTMLAreaAccessible::ChildAtPoint(int32_t aX, int32_t aY,
+ EWhichChildAtPoint aWhichChild)
+{
+ // Don't walk into area accessibles.
+ return this;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLImageMapAccessible: HyperLinkAccessible
+
+uint32_t
+HTMLAreaAccessible::StartOffset()
+{
+ // Image map accessible is not hypertext accessible therefore
+ // StartOffset/EndOffset implementations of Accessible doesn't work here.
+ // We return index in parent because image map contains area links only which
+ // are embedded objects.
+ // XXX: image map should be a hypertext accessible.
+ return IndexInParent();
+}
+
+uint32_t
+HTMLAreaAccessible::EndOffset()
+{
+ return IndexInParent() + 1;
+}
+
+nsRect
+HTMLAreaAccessible::RelativeBounds(nsIFrame** aBoundingFrame) const
+{
+ nsIFrame* frame = GetFrame();
+ if (!frame)
+ return nsRect();
+
+ nsImageFrame* imageFrame = do_QueryFrame(frame);
+ nsImageMap* map = imageFrame->GetImageMap();
+
+ nsRect bounds;
+ nsresult rv = map->GetBoundsForAreaContent(mContent, bounds);
+ if (NS_FAILED(rv))
+ return nsRect();
+
+ // XXX Areas are screwy; they return their rects as a pair of points, one pair
+ // stored into the width and height.
+ *aBoundingFrame = frame;
+ bounds.width -= bounds.x;
+ bounds.height -= bounds.y;
+ return bounds;
+}
diff --git a/accessible/html/HTMLImageMapAccessible.h b/accessible/html/HTMLImageMapAccessible.h
new file mode 100644
index 000000000..1668245dc
--- /dev/null
+++ b/accessible/html/HTMLImageMapAccessible.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_HTMLImageMapAccessible_h__
+#define mozilla_a11y_HTMLImageMapAccessible_h__
+
+#include "HTMLLinkAccessible.h"
+#include "ImageAccessibleWrap.h"
+#include "nsIDOMHTMLMapElement.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Used for HTML image maps.
+ */
+class HTMLImageMapAccessible final : public ImageAccessibleWrap
+{
+public:
+ HTMLImageMapAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // nsISupports and cycle collector
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+
+ // HyperLinkAccessible
+ virtual uint32_t AnchorCount() override;
+ virtual Accessible* AnchorAt(uint32_t aAnchorIndex) override;
+ virtual already_AddRefed<nsIURI> AnchorURIAt(uint32_t aAnchorIndex) override;
+
+ /**
+ * Update area children of the image map.
+ */
+ void UpdateChildAreas(bool aDoFireEvents = true);
+
+ /**
+ * Return accessible of child node.
+ */
+ Accessible* GetChildAccessibleFor(const nsINode* aNode) const;
+
+protected:
+ virtual ~HTMLImageMapAccessible() { }
+};
+
+/**
+ * Accessible for image map areas - must be child of image.
+ */
+class HTMLAreaAccessible final : public HTMLLinkAccessible
+{
+public:
+
+ HTMLAreaAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual void Description(nsString& aDescription) override;
+ virtual Accessible* ChildAtPoint(int32_t aX, int32_t aY,
+ EWhichChildAtPoint aWhichChild) override;
+ virtual nsRect RelativeBounds(nsIFrame** aBoundingFrame) const override;
+
+ // HyperLinkAccessible
+ virtual uint32_t StartOffset() override;
+ virtual uint32_t EndOffset() override;
+
+ virtual bool IsAcceptableChild(nsIContent* aEl) const override
+ { return false; }
+
+protected:
+ // Accessible
+ virtual ENameValueFlag NativeName(nsString& aName) override;
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible downcasting method
+
+inline HTMLImageMapAccessible*
+Accessible::AsImageMap()
+{
+ return IsImageMap() ? static_cast<HTMLImageMapAccessible*>(this) : nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/html/HTMLLinkAccessible.cpp b/accessible/html/HTMLLinkAccessible.cpp
new file mode 100644
index 000000000..6172c857b
--- /dev/null
+++ b/accessible/html/HTMLLinkAccessible.cpp
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "HTMLLinkAccessible.h"
+
+#include "nsCoreUtils.h"
+#include "DocAccessible.h"
+#include "Role.h"
+#include "States.h"
+
+#include "nsContentUtils.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLLinkAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLLinkAccessible::
+ HTMLLinkAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HyperTextAccessibleWrap(aContent, aDoc)
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(HTMLLinkAccessible, HyperTextAccessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIAccessible
+
+role
+HTMLLinkAccessible::NativeRole()
+{
+ return roles::LINK;
+}
+
+uint64_t
+HTMLLinkAccessible::NativeState()
+{
+ return HyperTextAccessibleWrap::NativeState() & ~states::READONLY;
+}
+
+uint64_t
+HTMLLinkAccessible::NativeLinkState() const
+{
+ EventStates eventState = mContent->AsElement()->State();
+ if (eventState.HasState(NS_EVENT_STATE_UNVISITED))
+ return states::LINKED;
+
+ if (eventState.HasState(NS_EVENT_STATE_VISITED))
+ return states::LINKED | states::TRAVERSED;
+
+ // This is a either named anchor (a link with also a name attribute) or
+ // it doesn't have any attributes. Check if 'click' event handler is
+ // registered, otherwise bail out.
+ return nsCoreUtils::HasClickListener(mContent) ? states::LINKED : 0;
+}
+
+uint64_t
+HTMLLinkAccessible::NativeInteractiveState() const
+{
+ uint64_t state = HyperTextAccessibleWrap::NativeInteractiveState();
+
+ // This is how we indicate it is a named anchor. In other words, this anchor
+ // can be selected as a location :) There is no other better state to use to
+ // indicate this.
+ if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::name))
+ state |= states::SELECTABLE;
+
+ return state;
+}
+
+void
+HTMLLinkAccessible::Value(nsString& aValue)
+{
+ aValue.Truncate();
+
+ HyperTextAccessible::Value(aValue);
+ if (aValue.IsEmpty())
+ nsContentUtils::GetLinkLocation(mContent->AsElement(), aValue);
+}
+
+uint8_t
+HTMLLinkAccessible::ActionCount()
+{
+ return IsLinked() ? 1 : HyperTextAccessible::ActionCount();
+}
+
+void
+HTMLLinkAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ aName.Truncate();
+
+ if (!IsLinked()) {
+ HyperTextAccessible::ActionNameAt(aIndex, aName);
+ return;
+ }
+
+ // Action 0 (default action): Jump to link
+ if (aIndex == eAction_Jump)
+ aName.AssignLiteral("jump");
+}
+
+bool
+HTMLLinkAccessible::DoAction(uint8_t aIndex)
+{
+ if (!IsLinked())
+ return HyperTextAccessible::DoAction(aIndex);
+
+ // Action 0 (default action): Jump to link
+ if (aIndex != eAction_Jump)
+ return false;
+
+ DoCommand();
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HyperLinkAccessible
+
+bool
+HTMLLinkAccessible::IsLink()
+{
+ // Expose HyperLinkAccessible unconditionally.
+ return true;
+}
+
+already_AddRefed<nsIURI>
+HTMLLinkAccessible::AnchorURIAt(uint32_t aAnchorIndex)
+{
+ return aAnchorIndex == 0 ? mContent->GetHrefURI() : nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Protected members
+
+bool
+HTMLLinkAccessible::IsLinked() const
+{
+ EventStates state = mContent->AsElement()->State();
+ return state.HasAtLeastOneOfStates(NS_EVENT_STATE_VISITED |
+ NS_EVENT_STATE_UNVISITED);
+}
diff --git a/accessible/html/HTMLLinkAccessible.h b/accessible/html/HTMLLinkAccessible.h
new file mode 100644
index 000000000..aa4da22be
--- /dev/null
+++ b/accessible/html/HTMLLinkAccessible.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_HTMLLinkAccessible_h__
+#define mozilla_a11y_HTMLLinkAccessible_h__
+
+#include "HyperTextAccessibleWrap.h"
+
+namespace mozilla {
+namespace a11y {
+
+class HTMLLinkAccessible : public HyperTextAccessibleWrap
+{
+public:
+ HTMLLinkAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual void Value(nsString& aValue) override;
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+ virtual uint64_t NativeLinkState() const override;
+ virtual uint64_t NativeInteractiveState() const override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+
+ // HyperLinkAccessible
+ virtual bool IsLink() override;
+ virtual already_AddRefed<nsIURI> AnchorURIAt(uint32_t aAnchorIndex) override;
+
+protected:
+ virtual ~HTMLLinkAccessible() {}
+
+ enum { eAction_Jump = 0 };
+
+ /**
+ * Returns true if the link has href attribute.
+ */
+ bool IsLinked() const;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/html/HTMLListAccessible.cpp b/accessible/html/HTMLListAccessible.cpp
new file mode 100644
index 000000000..d5de25718
--- /dev/null
+++ b/accessible/html/HTMLListAccessible.cpp
@@ -0,0 +1,199 @@
+/* -*- 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 "HTMLListAccessible.h"
+
+#include "DocAccessible.h"
+#include "nsAccUtils.h"
+#include "Role.h"
+#include "States.h"
+
+#include "nsBlockFrame.h"
+#include "nsBulletFrame.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLListAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS_INHERITED0(HTMLListAccessible, HyperTextAccessible)
+
+role
+HTMLListAccessible::NativeRole()
+{
+ a11y::role r = GetAccService()->MarkupRole(mContent);
+ return r != roles::NOTHING ? r : roles::LIST;
+}
+
+uint64_t
+HTMLListAccessible::NativeState()
+{
+ return HyperTextAccessibleWrap::NativeState() | states::READONLY;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLLIAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLLIAccessible::
+ HTMLLIAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HyperTextAccessibleWrap(aContent, aDoc), mBullet(nullptr)
+{
+ mType = eHTMLLiType;
+
+ nsBlockFrame* blockFrame = do_QueryFrame(GetFrame());
+ if (blockFrame && blockFrame->HasBullet()) {
+ mBullet = new HTMLListBulletAccessible(mContent, mDoc);
+ Document()->BindToDocument(mBullet, nullptr);
+ AppendChild(mBullet);
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(HTMLLIAccessible, HyperTextAccessible)
+
+void
+HTMLLIAccessible::Shutdown()
+{
+ mBullet = nullptr;
+
+ HyperTextAccessibleWrap::Shutdown();
+}
+
+role
+HTMLLIAccessible::NativeRole()
+{
+ a11y::role r = GetAccService()->MarkupRole(mContent);
+ return r != roles::NOTHING ? r : roles::LISTITEM;
+}
+
+uint64_t
+HTMLLIAccessible::NativeState()
+{
+ return HyperTextAccessibleWrap::NativeState() | states::READONLY;
+}
+
+nsIntRect
+HTMLLIAccessible::Bounds() const
+{
+ nsIntRect rect = AccessibleWrap::Bounds();
+ if (rect.IsEmpty() || !mBullet || mBullet->IsInside())
+ return rect;
+
+ nsIntRect bulletRect = mBullet->Bounds();
+
+ rect.width += rect.x - bulletRect.x;
+ rect.x = bulletRect.x; // Move x coordinate of list item over to cover bullet as well
+ return rect;
+}
+
+bool
+HTMLLIAccessible::InsertChildAt(uint32_t aIndex, Accessible* aChild)
+{
+ // Adjust index if there's a bullet.
+ if (mBullet && aIndex == 0 && aChild != mBullet) {
+ return HyperTextAccessible::InsertChildAt(aIndex + 1, aChild);
+ }
+
+ return HyperTextAccessible::InsertChildAt(aIndex, aChild);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLLIAccessible: public
+
+void
+HTMLLIAccessible::UpdateBullet(bool aHasBullet)
+{
+ if (aHasBullet == !!mBullet) {
+ NS_NOTREACHED("Bullet and accessible are in sync already!");
+ return;
+ }
+
+ TreeMutation mt(this);
+ if (aHasBullet) {
+ mBullet = new HTMLListBulletAccessible(mContent, mDoc);
+ mDoc->BindToDocument(mBullet, nullptr);
+ InsertChildAt(0, mBullet);
+ mt.AfterInsertion(mBullet);
+ }
+ else {
+ mt.BeforeRemoval(mBullet);
+ RemoveChild(mBullet);
+ mBullet = nullptr;
+ }
+ mt.Done();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLListBulletAccessible
+////////////////////////////////////////////////////////////////////////////////
+HTMLListBulletAccessible::
+ HTMLListBulletAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ LeafAccessible(aContent, aDoc)
+{
+ mGenericTypes |= eText;
+ mStateFlags |= eSharedNode;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLListBulletAccessible: Accessible
+
+nsIFrame*
+HTMLListBulletAccessible::GetFrame() const
+{
+ nsBlockFrame* blockFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ return blockFrame ? blockFrame->GetBullet() : nullptr;
+}
+
+ENameValueFlag
+HTMLListBulletAccessible::Name(nsString &aName)
+{
+ aName.Truncate();
+
+ // Native anonymous content, ARIA can't be used. Get list bullet text.
+ nsBlockFrame* blockFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (blockFrame) {
+ blockFrame->GetSpokenBulletText(aName);
+ }
+
+ return eNameOK;
+}
+
+role
+HTMLListBulletAccessible::NativeRole()
+{
+ return roles::STATICTEXT;
+}
+
+uint64_t
+HTMLListBulletAccessible::NativeState()
+{
+ return LeafAccessible::NativeState() | states::READONLY;
+}
+
+void
+HTMLListBulletAccessible::AppendTextTo(nsAString& aText, uint32_t aStartOffset,
+ uint32_t aLength)
+{
+ nsAutoString bulletText;
+ nsBlockFrame* blockFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (blockFrame)
+ blockFrame->GetSpokenBulletText(bulletText);
+
+ aText.Append(Substring(bulletText, aStartOffset, aLength));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLListBulletAccessible: public
+
+bool
+HTMLListBulletAccessible::IsInside() const
+{
+ nsBlockFrame* blockFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ return blockFrame ? blockFrame->HasInsideBullet() : false;
+}
diff --git a/accessible/html/HTMLListAccessible.h b/accessible/html/HTMLListAccessible.h
new file mode 100644
index 000000000..ad4a2d425
--- /dev/null
+++ b/accessible/html/HTMLListAccessible.h
@@ -0,0 +1,105 @@
+/* -*- 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_HTMLListAccessible_h__
+#define mozilla_a11y_HTMLListAccessible_h__
+
+#include "BaseAccessibles.h"
+#include "HyperTextAccessibleWrap.h"
+
+namespace mozilla {
+namespace a11y {
+
+class HTMLListBulletAccessible;
+
+/**
+ * Used for HTML list (like HTML ul).
+ */
+class HTMLListAccessible : public HyperTextAccessibleWrap
+{
+public:
+ HTMLListAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HyperTextAccessibleWrap(aContent, aDoc) { mGenericTypes |= eList; }
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+
+protected:
+ virtual ~HTMLListAccessible() { }
+};
+
+
+/**
+ * Used for HTML list item (e.g. HTML li).
+ */
+class HTMLLIAccessible : public HyperTextAccessibleWrap
+{
+public:
+ HTMLLIAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual void Shutdown() override;
+ virtual nsIntRect Bounds() const override;
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+
+ virtual bool InsertChildAt(uint32_t aIndex, Accessible* aChild) override;
+
+ // HTMLLIAccessible
+ HTMLListBulletAccessible* Bullet() const { return mBullet; }
+ void UpdateBullet(bool aHasBullet);
+
+protected:
+ virtual ~HTMLLIAccessible() { }
+
+private:
+ RefPtr<HTMLListBulletAccessible> mBullet;
+};
+
+
+/**
+ * Used for bullet of HTML list item element (for example, HTML li).
+ */
+class HTMLListBulletAccessible : public LeafAccessible
+{
+public:
+ HTMLListBulletAccessible(nsIContent* aContent, DocAccessible* aDoc);
+ virtual ~HTMLListBulletAccessible() { }
+
+ // Accessible
+ virtual nsIFrame* GetFrame() const override;
+ virtual ENameValueFlag Name(nsString& aName) override;
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+ virtual void AppendTextTo(nsAString& aText, uint32_t aStartOffset = 0,
+ uint32_t aLength = UINT32_MAX) override;
+
+ // HTMLListBulletAccessible
+
+ /**
+ * Return true if the bullet is inside of list item element boundaries.
+ */
+ bool IsInside() const;
+};
+
+
+inline HTMLLIAccessible*
+Accessible::AsHTMLListItem()
+{
+ return IsHTMLListItem() ? static_cast<HTMLLIAccessible*>(this) : nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/html/HTMLSelectAccessible.cpp b/accessible/html/HTMLSelectAccessible.cpp
new file mode 100644
index 000000000..cb98a0038
--- /dev/null
+++ b/accessible/html/HTMLSelectAccessible.cpp
@@ -0,0 +1,610 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "HTMLSelectAccessible.h"
+
+#include "Accessible-inl.h"
+#include "nsAccessibilityService.h"
+#include "nsAccUtils.h"
+#include "DocAccessible.h"
+#include "nsEventShell.h"
+#include "nsTextEquivUtils.h"
+#include "Role.h"
+#include "States.h"
+
+#include "nsCOMPtr.h"
+#include "mozilla/dom/HTMLOptionElement.h"
+#include "mozilla/dom/HTMLSelectElement.h"
+#include "nsIComboboxControlFrame.h"
+#include "nsContainerFrame.h"
+#include "nsIListControlFrame.h"
+
+using namespace mozilla::a11y;
+using namespace mozilla::dom;
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLSelectListAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLSelectListAccessible::
+ HTMLSelectListAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+ mGenericTypes |= eListControl | eSelect;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLSelectListAccessible: Accessible public
+
+uint64_t
+HTMLSelectListAccessible::NativeState()
+{
+ uint64_t state = AccessibleWrap::NativeState();
+ if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple))
+ state |= states::MULTISELECTABLE | states::EXTSELECTABLE;
+
+ return state;
+}
+
+role
+HTMLSelectListAccessible::NativeRole()
+{
+ return roles::LISTBOX;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLSelectListAccessible: SelectAccessible
+
+bool
+HTMLSelectListAccessible::SelectAll()
+{
+ return mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple) ?
+ AccessibleWrap::SelectAll() : false;
+}
+
+bool
+HTMLSelectListAccessible::UnselectAll()
+{
+ return mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple) ?
+ AccessibleWrap::UnselectAll() : false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLSelectListAccessible: Widgets
+
+bool
+HTMLSelectListAccessible::IsWidget() const
+{
+ return true;
+}
+
+bool
+HTMLSelectListAccessible::IsActiveWidget() const
+{
+ return FocusMgr()->HasDOMFocus(mContent);
+}
+
+bool
+HTMLSelectListAccessible::AreItemsOperable() const
+{
+ return true;
+}
+
+Accessible*
+HTMLSelectListAccessible::CurrentItem()
+{
+ nsIListControlFrame* listControlFrame = do_QueryFrame(GetFrame());
+ if (listControlFrame) {
+ nsCOMPtr<nsIContent> activeOptionNode = listControlFrame->GetCurrentOption();
+ if (activeOptionNode) {
+ DocAccessible* document = Document();
+ if (document)
+ return document->GetAccessible(activeOptionNode);
+ }
+ }
+ return nullptr;
+}
+
+void
+HTMLSelectListAccessible::SetCurrentItem(Accessible* aItem)
+{
+ aItem->GetContent()->SetAttr(kNameSpaceID_None,
+ nsGkAtoms::selected, NS_LITERAL_STRING("true"),
+ true);
+}
+
+bool
+HTMLSelectListAccessible::IsAcceptableChild(nsIContent* aEl) const
+{
+ return aEl->IsAnyOfHTMLElements(nsGkAtoms::option, nsGkAtoms::optgroup);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLSelectOptionAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLSelectOptionAccessible::
+ HTMLSelectOptionAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HyperTextAccessibleWrap(aContent, aDoc)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLSelectOptionAccessible: Accessible public
+
+role
+HTMLSelectOptionAccessible::NativeRole()
+{
+ if (GetCombobox())
+ return roles::COMBOBOX_OPTION;
+
+ return roles::OPTION;
+}
+
+ENameValueFlag
+HTMLSelectOptionAccessible::NativeName(nsString& aName)
+{
+ // CASE #1 -- great majority of the cases
+ // find the label attribute - this is what the W3C says we should use
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aName);
+ if (!aName.IsEmpty())
+ return eNameOK;
+
+ // CASE #2 -- no label parameter, get the first child,
+ // use it if it is a text node
+ nsIContent* text = mContent->GetFirstChild();
+ if (text && text->IsNodeOfType(nsINode::eTEXT)) {
+ nsTextEquivUtils::AppendTextEquivFromTextContent(text, &aName);
+ aName.CompressWhitespace();
+ return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
+ }
+
+ return eNameOK;
+}
+
+uint64_t
+HTMLSelectOptionAccessible::NativeState()
+{
+ // As a HTMLSelectOptionAccessible we can have the following states:
+ // SELECTABLE, SELECTED, FOCUSED, FOCUSABLE, OFFSCREEN
+ // Upcall to Accessible, but skip HyperTextAccessible impl
+ // because we don't want EDITABLE or SELECTABLE_TEXT
+ uint64_t state = Accessible::NativeState();
+
+ Accessible* select = GetSelect();
+ if (!select)
+ return state;
+
+ uint64_t selectState = select->State();
+ if (selectState & states::INVISIBLE)
+ return state;
+
+ // Are we selected?
+ HTMLOptionElement* option = HTMLOptionElement::FromContent(mContent);
+ bool selected = option && option->Selected();
+ if (selected)
+ state |= states::SELECTED;
+
+ if (selectState & states::OFFSCREEN) {
+ state |= states::OFFSCREEN;
+ } else if (selectState & states::COLLAPSED) {
+ // <select> is COLLAPSED: add OFFSCREEN, if not the currently
+ // visible option
+ if (!selected) {
+ state |= states::OFFSCREEN;
+ state ^= states::INVISIBLE;
+ } else {
+ // Clear offscreen and invisible for currently showing option
+ state &= ~(states::OFFSCREEN | states::INVISIBLE);
+ state |= selectState & states::OPAQUE1;
+ }
+ } else {
+ // XXX list frames are weird, don't rely on Accessible's general
+ // visibility implementation unless they get reimplemented in layout
+ state &= ~states::OFFSCREEN;
+ // <select> is not collapsed: compare bounds to calculate OFFSCREEN
+ Accessible* listAcc = Parent();
+ if (listAcc) {
+ nsIntRect optionRect = Bounds();
+ nsIntRect listRect = listAcc->Bounds();
+ if (optionRect.y < listRect.y ||
+ optionRect.y + optionRect.height > listRect.y + listRect.height) {
+ state |= states::OFFSCREEN;
+ }
+ }
+ }
+
+ return state;
+}
+
+uint64_t
+HTMLSelectOptionAccessible::NativeInteractiveState() const
+{
+ return NativelyUnavailable() ?
+ states::UNAVAILABLE : states::FOCUSABLE | states::SELECTABLE;
+}
+
+int32_t
+HTMLSelectOptionAccessible::GetLevelInternal()
+{
+ nsIContent* parentContent = mContent->GetParent();
+
+ int32_t level =
+ parentContent->NodeInfo()->Equals(nsGkAtoms::optgroup) ? 2 : 1;
+
+ if (level == 1 && Role() != roles::HEADING)
+ level = 0; // In a single level list, the level is irrelevant
+
+ return level;
+}
+
+nsRect
+HTMLSelectOptionAccessible::RelativeBounds(nsIFrame** aBoundingFrame) const
+{
+ Accessible* combobox = GetCombobox();
+ if (combobox && (combobox->State() & states::COLLAPSED))
+ return combobox->RelativeBounds(aBoundingFrame);
+
+ return HyperTextAccessibleWrap::RelativeBounds(aBoundingFrame);
+}
+
+void
+HTMLSelectOptionAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ if (aIndex == eAction_Select)
+ aName.AssignLiteral("select");
+}
+
+uint8_t
+HTMLSelectOptionAccessible::ActionCount()
+{
+ return 1;
+}
+
+bool
+HTMLSelectOptionAccessible::DoAction(uint8_t aIndex)
+{
+ if (aIndex != eAction_Select)
+ return false;
+
+ DoCommand();
+ return true;
+}
+
+void
+HTMLSelectOptionAccessible::SetSelected(bool aSelect)
+{
+ HTMLOptionElement* option = HTMLOptionElement::FromContent(mContent);
+ if (option)
+ option->SetSelected(aSelect);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLSelectOptionAccessible: Widgets
+
+Accessible*
+HTMLSelectOptionAccessible::ContainerWidget() const
+{
+ Accessible* parent = Parent();
+ if (parent && parent->IsHTMLOptGroup())
+ parent = parent->Parent();
+
+ return parent && parent->IsListControl() ? parent : nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLSelectOptGroupAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+role
+HTMLSelectOptGroupAccessible::NativeRole()
+{
+ return roles::GROUPING;
+}
+
+uint64_t
+HTMLSelectOptGroupAccessible::NativeInteractiveState() const
+{
+ return NativelyUnavailable() ? states::UNAVAILABLE : 0;
+}
+
+uint8_t
+HTMLSelectOptGroupAccessible::ActionCount()
+{
+ return 0;
+}
+
+void
+HTMLSelectOptGroupAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ aName.Truncate();
+}
+
+bool
+HTMLSelectOptGroupAccessible::DoAction(uint8_t aIndex)
+{
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLComboboxAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLComboboxAccessible::
+ HTMLComboboxAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+ mType = eHTMLComboboxType;
+ mGenericTypes |= eCombobox;
+ mStateFlags |= eNoKidsFromDOM;
+
+ nsIComboboxControlFrame* comboFrame = do_QueryFrame(GetFrame());
+ if (comboFrame) {
+ nsIFrame* listFrame = comboFrame->GetDropDown();
+ if (listFrame) {
+ mListAccessible = new HTMLComboboxListAccessible(mParent, mContent, mDoc);
+ Document()->BindToDocument(mListAccessible, nullptr);
+ AppendChild(mListAccessible);
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLComboboxAccessible: Accessible
+
+role
+HTMLComboboxAccessible::NativeRole()
+{
+ return roles::COMBOBOX;
+}
+
+bool
+HTMLComboboxAccessible::RemoveChild(Accessible* aChild)
+{
+ MOZ_ASSERT(aChild == mListAccessible);
+ if (AccessibleWrap::RemoveChild(aChild)) {
+ mListAccessible = nullptr;
+ return true;
+ }
+ return false;
+}
+
+void
+HTMLComboboxAccessible::Shutdown()
+{
+ MOZ_ASSERT(mDoc->IsDefunct() || !mListAccessible);
+ if (mListAccessible) {
+ mListAccessible->Shutdown();
+ mListAccessible = nullptr;
+ }
+
+ AccessibleWrap::Shutdown();
+}
+
+uint64_t
+HTMLComboboxAccessible::NativeState()
+{
+ // As a HTMLComboboxAccessible we can have the following states:
+ // FOCUSED, FOCUSABLE, HASPOPUP, EXPANDED, COLLAPSED
+ // Get focus status from base class
+ uint64_t state = Accessible::NativeState();
+
+ nsIComboboxControlFrame* comboFrame = do_QueryFrame(GetFrame());
+ if (comboFrame && comboFrame->IsDroppedDown())
+ state |= states::EXPANDED;
+ else
+ state |= states::COLLAPSED;
+
+ state |= states::HASPOPUP;
+ return state;
+}
+
+void
+HTMLComboboxAccessible::Description(nsString& aDescription)
+{
+ aDescription.Truncate();
+ // First check to see if combo box itself has a description, perhaps through
+ // tooltip (title attribute) or via aria-describedby
+ Accessible::Description(aDescription);
+ if (!aDescription.IsEmpty())
+ return;
+
+ // Otherwise use description of selected option.
+ Accessible* option = SelectedOption();
+ if (option)
+ option->Description(aDescription);
+}
+
+void
+HTMLComboboxAccessible::Value(nsString& aValue)
+{
+ // Use accessible name of selected option.
+ Accessible* option = SelectedOption();
+ if (option)
+ option->Name(aValue);
+}
+
+uint8_t
+HTMLComboboxAccessible::ActionCount()
+{
+ return 1;
+}
+
+bool
+HTMLComboboxAccessible::DoAction(uint8_t aIndex)
+{
+ if (aIndex != eAction_Click)
+ return false;
+
+ DoCommand();
+ return true;
+}
+
+void
+HTMLComboboxAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ if (aIndex != HTMLComboboxAccessible::eAction_Click)
+ return;
+
+ nsIComboboxControlFrame* comboFrame = do_QueryFrame(GetFrame());
+ if (!comboFrame)
+ return;
+
+ if (comboFrame->IsDroppedDown())
+ aName.AssignLiteral("close");
+ else
+ aName.AssignLiteral("open");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLComboboxAccessible: Widgets
+
+bool
+HTMLComboboxAccessible::IsWidget() const
+{
+ return true;
+}
+
+bool
+HTMLComboboxAccessible::IsActiveWidget() const
+{
+ return FocusMgr()->HasDOMFocus(mContent);
+}
+
+bool
+HTMLComboboxAccessible::AreItemsOperable() const
+{
+ nsIComboboxControlFrame* comboboxFrame = do_QueryFrame(GetFrame());
+ return comboboxFrame && comboboxFrame->IsDroppedDown();
+}
+
+Accessible*
+HTMLComboboxAccessible::CurrentItem()
+{
+ return AreItemsOperable() ? mListAccessible->CurrentItem() : nullptr;
+}
+
+void
+HTMLComboboxAccessible::SetCurrentItem(Accessible* aItem)
+{
+ if (AreItemsOperable())
+ mListAccessible->SetCurrentItem(aItem);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLComboboxAccessible: protected
+
+Accessible*
+HTMLComboboxAccessible::SelectedOption() const
+{
+ HTMLSelectElement* select = HTMLSelectElement::FromContent(mContent);
+ int32_t selectedIndex = select->SelectedIndex();
+
+ if (selectedIndex >= 0) {
+ HTMLOptionElement* option = select->Item(selectedIndex);
+ if (option) {
+ DocAccessible* document = Document();
+ if (document)
+ return document->GetAccessible(option);
+ }
+ }
+
+ return nullptr;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLComboboxListAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLComboboxListAccessible::
+ HTMLComboboxListAccessible(Accessible* aParent, nsIContent* aContent,
+ DocAccessible* aDoc) :
+ HTMLSelectListAccessible(aContent, aDoc)
+{
+ mStateFlags |= eSharedNode;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLComboboxAccessible: Accessible
+
+nsIFrame*
+HTMLComboboxListAccessible::GetFrame() const
+{
+ nsIFrame* frame = HTMLSelectListAccessible::GetFrame();
+ nsIComboboxControlFrame* comboBox = do_QueryFrame(frame);
+ if (comboBox) {
+ return comboBox->GetDropDown();
+ }
+
+ return nullptr;
+}
+
+role
+HTMLComboboxListAccessible::NativeRole()
+{
+ return roles::COMBOBOX_LIST;
+}
+
+uint64_t
+HTMLComboboxListAccessible::NativeState()
+{
+ // As a HTMLComboboxListAccessible we can have the following states:
+ // FOCUSED, FOCUSABLE, FLOATING, INVISIBLE
+ // Get focus status from base class
+ uint64_t state = Accessible::NativeState();
+
+ nsIComboboxControlFrame* comboFrame = do_QueryFrame(mParent->GetFrame());
+ if (comboFrame && comboFrame->IsDroppedDown())
+ state |= states::FLOATING;
+ else
+ state |= states::INVISIBLE;
+
+ return state;
+}
+
+nsRect
+HTMLComboboxListAccessible::RelativeBounds(nsIFrame** aBoundingFrame) const
+{
+ *aBoundingFrame = nullptr;
+
+ Accessible* comboAcc = Parent();
+ if (!comboAcc)
+ return nsRect();
+
+ if (0 == (comboAcc->State() & states::COLLAPSED)) {
+ return HTMLSelectListAccessible::RelativeBounds(aBoundingFrame);
+ }
+
+ // Get the first option.
+ nsIContent* content = mContent->GetFirstChild();
+ if (!content)
+ return nsRect();
+
+ nsIFrame* frame = content->GetPrimaryFrame();
+ if (!frame) {
+ *aBoundingFrame = nullptr;
+ return nsRect();
+ }
+
+ *aBoundingFrame = frame->GetParent();
+ return (*aBoundingFrame)->GetRect();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLComboboxListAccessible: Widgets
+
+bool
+HTMLComboboxListAccessible::IsActiveWidget() const
+{
+ return mParent && mParent->IsActiveWidget();
+}
+
+bool
+HTMLComboboxListAccessible::AreItemsOperable() const
+{
+ return mParent && mParent->AreItemsOperable();
+}
+
diff --git a/accessible/html/HTMLSelectAccessible.h b/accessible/html/HTMLSelectAccessible.h
new file mode 100644
index 000000000..0c781034f
--- /dev/null
+++ b/accessible/html/HTMLSelectAccessible.h
@@ -0,0 +1,222 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_HTMLSelectAccessible_h__
+#define mozilla_a11y_HTMLSelectAccessible_h__
+
+#include "HTMLFormControlAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Selects, Listboxes and Comboboxes, are made up of a number of different
+ * widgets, some of which are shared between the two. This file contains
+ * all of the widgets for both of the Selects, for HTML only.
+ *
+ * Listbox:
+ * - HTMLSelectListAccessible
+ * - HTMLSelectOptionAccessible
+ *
+ * Comboboxes:
+ * - HTMLComboboxAccessible
+ * - HTMLComboboxListAccessible [ inserted in accessible tree ]
+ * - HTMLSelectOptionAccessible(s)
+ */
+
+/*
+ * The list that contains all the options in the select.
+ */
+class HTMLSelectListAccessible : public AccessibleWrap
+{
+public:
+
+ HTMLSelectListAccessible(nsIContent* aContent, DocAccessible* aDoc);
+ virtual ~HTMLSelectListAccessible() {}
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+
+ // SelectAccessible
+ virtual bool SelectAll() override;
+ virtual bool UnselectAll() override;
+
+ // Widgets
+ virtual bool IsWidget() const override;
+ virtual bool IsActiveWidget() const override;
+ virtual bool AreItemsOperable() const override;
+ virtual Accessible* CurrentItem() override;
+ virtual void SetCurrentItem(Accessible* aItem) override;
+
+ virtual bool IsAcceptableChild(nsIContent* aEl) const override;
+};
+
+/*
+ * Options inside the select, contained within the list
+ */
+class HTMLSelectOptionAccessible : public HyperTextAccessibleWrap
+{
+public:
+ enum { eAction_Select = 0 };
+
+ HTMLSelectOptionAccessible(nsIContent* aContent, DocAccessible* aDoc);
+ virtual ~HTMLSelectOptionAccessible() {}
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+ virtual uint64_t NativeInteractiveState() const override;
+
+ virtual int32_t GetLevelInternal() override;
+ virtual nsRect RelativeBounds(nsIFrame** aBoundingFrame) const override;
+ virtual void SetSelected(bool aSelect) override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+
+ // Widgets
+ virtual Accessible* ContainerWidget() const override;
+
+protected:
+ // Accessible
+ virtual ENameValueFlag NativeName(nsString& aName) override;
+
+private:
+
+ /**
+ * Return a select accessible the option belongs to if any.
+ */
+ Accessible* GetSelect() const
+ {
+ Accessible* parent = mParent;
+ if (parent && parent->IsHTMLOptGroup())
+ parent = parent->Parent();
+
+ if (parent && parent->IsListControl()) {
+ Accessible* combobox = parent->Parent();
+ return combobox && combobox->IsCombobox() ? combobox : mParent;
+ }
+
+ return nullptr;
+ }
+
+ /**
+ * Return a combobox accessible the option belongs to if any.
+ */
+ Accessible* GetCombobox() const
+ {
+ Accessible* parent = mParent;
+ if (parent && parent->IsHTMLOptGroup())
+ parent = parent->Parent();
+
+ if (parent && parent->IsListControl()) {
+ Accessible* combobox = parent->Parent();
+ return combobox && combobox->IsCombobox() ? combobox : nullptr;
+ }
+
+ return nullptr;
+ }
+};
+
+/*
+ * Opt Groups inside the select, contained within the list
+ */
+class HTMLSelectOptGroupAccessible : public HTMLSelectOptionAccessible
+{
+public:
+
+ HTMLSelectOptGroupAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HTMLSelectOptionAccessible(aContent, aDoc)
+ { mType = eHTMLOptGroupType; }
+ virtual ~HTMLSelectOptGroupAccessible() {}
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeInteractiveState() const override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+};
+
+/** ------------------------------------------------------ */
+/** Finally, the Combobox widgets */
+/** ------------------------------------------------------ */
+
+class HTMLComboboxListAccessible;
+
+/*
+ * A class the represents the HTML Combobox widget.
+ */
+class HTMLComboboxAccessible final : public AccessibleWrap
+{
+public:
+ enum { eAction_Click = 0 };
+
+ HTMLComboboxAccessible(nsIContent* aContent, DocAccessible* aDoc);
+ virtual ~HTMLComboboxAccessible() {}
+
+ // Accessible
+ virtual void Shutdown() override;
+ virtual void Description(nsString& aDescription) override;
+ virtual void Value(nsString& aValue) override;
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+ virtual bool RemoveChild(Accessible* aChild) override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+
+ // Widgets
+ virtual bool IsWidget() const override;
+ virtual bool IsActiveWidget() const override;
+ virtual bool AreItemsOperable() const override;
+ virtual Accessible* CurrentItem() override;
+ virtual void SetCurrentItem(Accessible* aItem) override;
+
+protected:
+ /**
+ * Return selected option.
+ */
+ Accessible* SelectedOption() const;
+
+private:
+ RefPtr<HTMLComboboxListAccessible> mListAccessible;
+};
+
+/*
+ * A class that represents the window that lives to the right
+ * of the drop down button inside the Select. This is the window
+ * that is made visible when the button is pressed.
+ */
+class HTMLComboboxListAccessible : public HTMLSelectListAccessible
+{
+public:
+
+ HTMLComboboxListAccessible(Accessible* aParent, nsIContent* aContent,
+ DocAccessible* aDoc);
+ virtual ~HTMLComboboxListAccessible() {}
+
+ // Accessible
+ virtual nsIFrame* GetFrame() const override;
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+ virtual nsRect RelativeBounds(nsIFrame** aBoundingFrame) const override;
+
+ // Widgets
+ virtual bool IsActiveWidget() const override;
+ virtual bool AreItemsOperable() const override;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/html/HTMLTableAccessible.cpp b/accessible/html/HTMLTableAccessible.cpp
new file mode 100644
index 000000000..b0cdc0932
--- /dev/null
+++ b/accessible/html/HTMLTableAccessible.cpp
@@ -0,0 +1,1139 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "HTMLTableAccessible.h"
+
+#include "mozilla/DebugOnly.h"
+
+#include "Accessible-inl.h"
+#include "nsAccessibilityService.h"
+#include "nsAccUtils.h"
+#include "DocAccessible.h"
+#include "nsTextEquivUtils.h"
+#include "Relation.h"
+#include "Role.h"
+#include "States.h"
+#include "TreeWalker.h"
+
+#include "mozilla/dom/HTMLTableElement.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMRange.h"
+#include "nsISelectionPrivate.h"
+#include "nsIDOMNodeList.h"
+#include "nsIDOMHTMLCollection.h"
+#include "nsIDocument.h"
+#include "nsIMutableArray.h"
+#include "nsIPersistentProperties2.h"
+#include "nsIPresShell.h"
+#include "nsITableCellLayout.h"
+#include "nsFrameSelection.h"
+#include "nsError.h"
+#include "nsArrayUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsNameSpaceManager.h"
+#include "nsTableCellFrame.h"
+#include "nsTableWrapperFrame.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableCellAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLTableCellAccessible::
+ HTMLTableCellAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HyperTextAccessibleWrap(aContent, aDoc)
+{
+ mType = eHTMLTableCellType;
+ mGenericTypes |= eTableCell;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(HTMLTableCellAccessible, HyperTextAccessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableCellAccessible: Accessible implementation
+
+role
+HTMLTableCellAccessible::NativeRole()
+{
+ if (mContent->IsMathMLElement(nsGkAtoms::mtd_)) {
+ return roles::MATHML_CELL;
+ }
+ return roles::CELL;
+}
+
+uint64_t
+HTMLTableCellAccessible::NativeState()
+{
+ uint64_t state = HyperTextAccessibleWrap::NativeState();
+
+ nsIFrame *frame = mContent->GetPrimaryFrame();
+ NS_ASSERTION(frame, "No frame for valid cell accessible!");
+
+ if (frame && frame->IsSelected())
+ state |= states::SELECTED;
+
+ return state;
+}
+
+uint64_t
+HTMLTableCellAccessible::NativeInteractiveState() const
+{
+ return HyperTextAccessibleWrap::NativeInteractiveState() | states::SELECTABLE;
+}
+
+already_AddRefed<nsIPersistentProperties>
+HTMLTableCellAccessible::NativeAttributes()
+{
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ HyperTextAccessibleWrap::NativeAttributes();
+
+ // table-cell-index attribute
+ TableAccessible* table = Table();
+ if (!table)
+ return attributes.forget();
+
+ int32_t rowIdx = -1, colIdx = -1;
+ nsresult rv = GetCellIndexes(rowIdx, colIdx);
+ if (NS_FAILED(rv))
+ return attributes.forget();
+
+ nsAutoString stringIdx;
+ stringIdx.AppendInt(table->CellIndexAt(rowIdx, colIdx));
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::tableCellIndex, stringIdx);
+
+ // abbr attribute
+
+ // Pick up object attribute from abbr DOM element (a child of the cell) or
+ // from abbr DOM attribute.
+ nsAutoString abbrText;
+ if (ChildCount() == 1) {
+ Accessible* abbr = FirstChild();
+ if (abbr->IsAbbreviation()) {
+ nsIContent* firstChildNode = abbr->GetContent()->GetFirstChild();
+ if (firstChildNode) {
+ nsTextEquivUtils::
+ AppendTextEquivFromTextContent(firstChildNode, &abbrText);
+ }
+ }
+ }
+ if (abbrText.IsEmpty())
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::abbr, abbrText);
+
+ if (!abbrText.IsEmpty())
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::abbr, abbrText);
+
+ // axis attribute
+ nsAutoString axisText;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::axis, axisText);
+ if (!axisText.IsEmpty())
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::axis, axisText);
+
+#ifdef DEBUG
+ nsAutoString unused;
+ attributes->SetStringProperty(NS_LITERAL_CSTRING("cppclass"),
+ NS_LITERAL_STRING("HTMLTableCellAccessible"),
+ unused);
+#endif
+
+ return attributes.forget();
+}
+
+GroupPos
+HTMLTableCellAccessible::GroupPosition()
+{
+ int32_t count = 0, index = 0;
+ TableAccessible* table = Table();
+ if (table && nsCoreUtils::GetUIntAttr(table->AsAccessible()->GetContent(),
+ nsGkAtoms::aria_colcount, &count) &&
+ nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_colindex, &index)) {
+ return GroupPos(0, index, count);
+ }
+
+ return HyperTextAccessibleWrap::GroupPosition();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableCellAccessible: TableCellAccessible implementation
+
+TableAccessible*
+HTMLTableCellAccessible::Table() const
+{
+ Accessible* parent = const_cast<HTMLTableCellAccessible*>(this);
+ while ((parent = parent->Parent())) {
+ if (parent->IsTable())
+ return parent->AsTable();
+ }
+
+ return nullptr;
+}
+
+uint32_t
+HTMLTableCellAccessible::ColIdx() const
+{
+ nsITableCellLayout* cellLayout = GetCellLayout();
+ NS_ENSURE_TRUE(cellLayout, 0);
+
+ int32_t colIdx = 0;
+ cellLayout->GetColIndex(colIdx);
+ return colIdx > 0 ? static_cast<uint32_t>(colIdx) : 0;
+}
+
+uint32_t
+HTMLTableCellAccessible::RowIdx() const
+{
+ nsITableCellLayout* cellLayout = GetCellLayout();
+ NS_ENSURE_TRUE(cellLayout, 0);
+
+ int32_t rowIdx = 0;
+ cellLayout->GetRowIndex(rowIdx);
+ return rowIdx > 0 ? static_cast<uint32_t>(rowIdx) : 0;
+}
+
+uint32_t
+HTMLTableCellAccessible::ColExtent() const
+{
+ int32_t rowIdx = -1, colIdx = -1;
+ GetCellIndexes(rowIdx, colIdx);
+
+ TableAccessible* table = Table();
+ NS_ASSERTION(table, "cell not in a table!");
+ if (!table)
+ return 0;
+
+ return table->ColExtentAt(rowIdx, colIdx);
+}
+
+uint32_t
+HTMLTableCellAccessible::RowExtent() const
+{
+ int32_t rowIdx = -1, colIdx = -1;
+ GetCellIndexes(rowIdx, colIdx);
+
+ TableAccessible* table = Table();
+ NS_ASSERTION(table, "cell not in atable!");
+ if (!table)
+ return 0;
+
+ return table->RowExtentAt(rowIdx, colIdx);
+}
+
+void
+HTMLTableCellAccessible::ColHeaderCells(nsTArray<Accessible*>* aCells)
+{
+ IDRefsIterator itr(mDoc, mContent, nsGkAtoms::headers);
+ while (Accessible* cell = itr.Next()) {
+ a11y::role cellRole = cell->Role();
+ if (cellRole == roles::COLUMNHEADER) {
+ aCells->AppendElement(cell);
+ } else if (cellRole != roles::ROWHEADER) {
+ // If referred table cell is at the same column then treat it as a column
+ // header.
+ TableCellAccessible* tableCell = cell->AsTableCell();
+ if (tableCell && tableCell->ColIdx() == ColIdx())
+ aCells->AppendElement(cell);
+ }
+ }
+
+ if (aCells->IsEmpty())
+ TableCellAccessible::ColHeaderCells(aCells);
+}
+
+void
+HTMLTableCellAccessible::RowHeaderCells(nsTArray<Accessible*>* aCells)
+{
+ IDRefsIterator itr(mDoc, mContent, nsGkAtoms::headers);
+ while (Accessible* cell = itr.Next()) {
+ a11y::role cellRole = cell->Role();
+ if (cellRole == roles::ROWHEADER) {
+ aCells->AppendElement(cell);
+ } else if (cellRole != roles::COLUMNHEADER) {
+ // If referred table cell is at the same row then treat it as a column
+ // header.
+ TableCellAccessible* tableCell = cell->AsTableCell();
+ if (tableCell && tableCell->RowIdx() == RowIdx())
+ aCells->AppendElement(cell);
+ }
+ }
+
+ if (aCells->IsEmpty())
+ TableCellAccessible::RowHeaderCells(aCells);
+}
+
+bool
+HTMLTableCellAccessible::Selected()
+{
+ int32_t rowIdx = -1, colIdx = -1;
+ GetCellIndexes(rowIdx, colIdx);
+
+ TableAccessible* table = Table();
+ NS_ENSURE_TRUE(table, false);
+
+ return table->IsCellSelected(rowIdx, colIdx);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableCellAccessible: protected implementation
+
+nsITableCellLayout*
+HTMLTableCellAccessible::GetCellLayout() const
+{
+ return do_QueryFrame(mContent->GetPrimaryFrame());
+}
+
+nsresult
+HTMLTableCellAccessible::GetCellIndexes(int32_t& aRowIdx, int32_t& aColIdx) const
+{
+ nsITableCellLayout *cellLayout = GetCellLayout();
+ NS_ENSURE_STATE(cellLayout);
+
+ return cellLayout->GetCellIndexes(aRowIdx, aColIdx);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableHeaderCellAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLTableHeaderCellAccessible::
+ HTMLTableHeaderCellAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HTMLTableCellAccessible(aContent, aDoc)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableHeaderCellAccessible: Accessible implementation
+
+role
+HTMLTableHeaderCellAccessible::NativeRole()
+{
+ // Check value of @scope attribute.
+ static nsIContent::AttrValuesArray scopeValues[] =
+ { &nsGkAtoms::col, &nsGkAtoms::colgroup,
+ &nsGkAtoms::row, &nsGkAtoms::rowgroup, nullptr };
+ int32_t valueIdx =
+ mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::scope,
+ scopeValues, eCaseMatters);
+
+ switch (valueIdx) {
+ case 0:
+ case 1:
+ return roles::COLUMNHEADER;
+ case 2:
+ case 3:
+ return roles::ROWHEADER;
+ }
+
+ TableAccessible* table = Table();
+ if (!table)
+ return roles::NOTHING;
+
+ // If the cell next to this one is not a header cell then assume this cell is
+ // a row header for it.
+ uint32_t rowIdx = RowIdx(), colIdx = ColIdx();
+ Accessible* cell = table->CellAt(rowIdx, colIdx + ColExtent());
+ if (cell && !nsCoreUtils::IsHTMLTableHeader(cell->GetContent()))
+ return roles::ROWHEADER;
+
+ // If the cell below this one is not a header cell then assume this cell is
+ // a column header for it.
+ uint32_t rowExtent = RowExtent();
+ cell = table->CellAt(rowIdx + rowExtent, colIdx);
+ if (cell && !nsCoreUtils::IsHTMLTableHeader(cell->GetContent()))
+ return roles::COLUMNHEADER;
+
+ // Otherwise if this cell is surrounded by header cells only then make a guess
+ // based on its cell spanning. In other words if it is row spanned then assume
+ // it's a row header, otherwise it's a column header.
+ return rowExtent > 1 ? roles::ROWHEADER : roles::COLUMNHEADER;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableRowAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS_INHERITED0(HTMLTableRowAccessible, Accessible)
+
+role
+HTMLTableRowAccessible::NativeRole()
+{
+ if (mContent->IsMathMLElement(nsGkAtoms::mtr_)) {
+ return roles::MATHML_TABLE_ROW;
+ } else if (mContent->IsMathMLElement(nsGkAtoms::mlabeledtr_)) {
+ return roles::MATHML_LABELED_ROW;
+ }
+ return roles::ROW;
+}
+
+GroupPos
+HTMLTableRowAccessible::GroupPosition()
+{
+ int32_t count = 0, index = 0;
+ Accessible* table = nsAccUtils::TableFor(this);
+ if (table && nsCoreUtils::GetUIntAttr(table->GetContent(),
+ nsGkAtoms::aria_rowcount, &count) &&
+ nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_rowindex, &index)) {
+ return GroupPos(0, index, count);
+ }
+
+ return AccessibleWrap::GroupPosition();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS_INHERITED0(HTMLTableAccessible, Accessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableAccessible: Accessible
+
+bool
+HTMLTableAccessible::InsertChildAt(uint32_t aIndex, Accessible* aChild)
+{
+ // Move caption accessible so that it's the first child. Check for the first
+ // caption only, because nsAccessibilityService ensures we don't create
+ // accessibles for the other captions, since only the first is actually
+ // visible.
+ return Accessible::InsertChildAt(aChild->IsHTMLCaption() ? 0 : aIndex, aChild);
+}
+
+role
+HTMLTableAccessible::NativeRole()
+{
+ if (mContent->IsMathMLElement(nsGkAtoms::mtable_)) {
+ return roles::MATHML_TABLE;
+ }
+ return roles::TABLE;
+}
+
+uint64_t
+HTMLTableAccessible::NativeState()
+{
+ return Accessible::NativeState() | states::READONLY;
+}
+
+ENameValueFlag
+HTMLTableAccessible::NativeName(nsString& aName)
+{
+ ENameValueFlag nameFlag = Accessible::NativeName(aName);
+ if (!aName.IsEmpty())
+ return nameFlag;
+
+ // Use table caption as a name.
+ Accessible* caption = Caption();
+ if (caption) {
+ nsIContent* captionContent = caption->GetContent();
+ if (captionContent) {
+ nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent, &aName);
+ if (!aName.IsEmpty())
+ return eNameOK;
+ }
+ }
+
+ // If no caption then use summary as a name.
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::summary, aName);
+ return eNameOK;
+}
+
+already_AddRefed<nsIPersistentProperties>
+HTMLTableAccessible::NativeAttributes()
+{
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ AccessibleWrap::NativeAttributes();
+
+ if (mContent->IsMathMLElement(nsGkAtoms::mtable_)) {
+ GetAccService()->MarkupAttributes(mContent, attributes);
+ }
+
+ if (IsProbablyLayoutTable()) {
+ nsAutoString unused;
+ attributes->SetStringProperty(NS_LITERAL_CSTRING("layout-guess"),
+ NS_LITERAL_STRING("true"), unused);
+ }
+
+ return attributes.forget();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableAccessible: Accessible
+
+Relation
+HTMLTableAccessible::RelationByType(RelationType aType)
+{
+ Relation rel = AccessibleWrap::RelationByType(aType);
+ if (aType == RelationType::LABELLED_BY)
+ rel.AppendTarget(Caption());
+
+ return rel;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableAccessible: Table
+
+Accessible*
+HTMLTableAccessible::Caption() const
+{
+ Accessible* child = mChildren.SafeElementAt(0, nullptr);
+ return child && child->Role() == roles::CAPTION ? child : nullptr;
+}
+
+void
+HTMLTableAccessible::Summary(nsString& aSummary)
+{
+ dom::HTMLTableElement* table = dom::HTMLTableElement::FromContent(mContent);
+
+ if (table)
+ table->GetSummary(aSummary);
+}
+
+uint32_t
+HTMLTableAccessible::ColCount()
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ return tableFrame ? tableFrame->GetColCount() : 0;
+}
+
+uint32_t
+HTMLTableAccessible::RowCount()
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ return tableFrame ? tableFrame->GetRowCount() : 0;
+}
+
+uint32_t
+HTMLTableAccessible::SelectedCellCount()
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ return 0;
+
+ uint32_t count = 0, rowCount = RowCount(), colCount = ColCount();
+ for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
+ nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx);
+ if (!cellFrame || !cellFrame->IsSelected())
+ continue;
+
+ int32_t startRow = -1, startCol = -1;
+ cellFrame->GetRowIndex(startRow);
+ cellFrame->GetColIndex(startCol);
+ if (startRow >= 0 && (uint32_t)startRow == rowIdx &&
+ startCol >= 0 && (uint32_t)startCol == colIdx)
+ count++;
+ }
+ }
+
+ return count;
+}
+
+uint32_t
+HTMLTableAccessible::SelectedColCount()
+{
+ uint32_t count = 0, colCount = ColCount();
+
+ for (uint32_t colIdx = 0; colIdx < colCount; colIdx++)
+ if (IsColSelected(colIdx))
+ count++;
+
+ return count;
+}
+
+uint32_t
+HTMLTableAccessible::SelectedRowCount()
+{
+ uint32_t count = 0, rowCount = RowCount();
+
+ for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++)
+ if (IsRowSelected(rowIdx))
+ count++;
+
+ return count;
+}
+
+void
+HTMLTableAccessible::SelectedCells(nsTArray<Accessible*>* aCells)
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ return;
+
+ uint32_t rowCount = RowCount(), colCount = ColCount();
+ for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
+ nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx);
+ if (!cellFrame || !cellFrame->IsSelected())
+ continue;
+
+ int32_t startCol = -1, startRow = -1;
+ cellFrame->GetRowIndex(startRow);
+ cellFrame->GetColIndex(startCol);
+ if ((startRow >= 0 && (uint32_t)startRow != rowIdx) ||
+ (startCol >= 0 && (uint32_t)startCol != colIdx))
+ continue;
+
+ Accessible* cell = mDoc->GetAccessible(cellFrame->GetContent());
+ aCells->AppendElement(cell);
+ }
+ }
+}
+
+void
+HTMLTableAccessible::SelectedCellIndices(nsTArray<uint32_t>* aCells)
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ return;
+
+ uint32_t rowCount = RowCount(), colCount = ColCount();
+ for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
+ nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx);
+ if (!cellFrame || !cellFrame->IsSelected())
+ continue;
+
+ int32_t startRow = -1, startCol = -1;
+ cellFrame->GetColIndex(startCol);
+ cellFrame->GetRowIndex(startRow);
+ if (startRow >= 0 && (uint32_t)startRow == rowIdx &&
+ startCol >= 0 && (uint32_t)startCol == colIdx)
+ aCells->AppendElement(CellIndexAt(rowIdx, colIdx));
+ }
+ }
+}
+
+void
+HTMLTableAccessible::SelectedColIndices(nsTArray<uint32_t>* aCols)
+{
+ uint32_t colCount = ColCount();
+ for (uint32_t colIdx = 0; colIdx < colCount; colIdx++)
+ if (IsColSelected(colIdx))
+ aCols->AppendElement(colIdx);
+}
+
+void
+HTMLTableAccessible::SelectedRowIndices(nsTArray<uint32_t>* aRows)
+{
+ uint32_t rowCount = RowCount();
+ for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++)
+ if (IsRowSelected(rowIdx))
+ aRows->AppendElement(rowIdx);
+}
+
+Accessible*
+HTMLTableAccessible::CellAt(uint32_t aRowIdx, uint32_t aColIdx)
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ return nullptr;
+
+ nsIContent* cellContent = tableFrame->GetCellAt(aRowIdx, aColIdx);
+ Accessible* cell = mDoc->GetAccessible(cellContent);
+
+ // XXX bug 576838: crazy tables (like table6 in tables/test_table2.html) may
+ // return itself as a cell what makes Orca hang.
+ return cell == this ? nullptr : cell;
+}
+
+int32_t
+HTMLTableAccessible::CellIndexAt(uint32_t aRowIdx, uint32_t aColIdx)
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ return -1;
+
+ return tableFrame->GetIndexByRowAndColumn(aRowIdx, aColIdx);
+}
+
+int32_t
+HTMLTableAccessible::ColIndexAt(uint32_t aCellIdx)
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ return -1;
+
+ int32_t rowIdx = -1, colIdx = -1;
+ tableFrame->GetRowAndColumnByIndex(aCellIdx, &rowIdx, &colIdx);
+ return colIdx;
+}
+
+int32_t
+HTMLTableAccessible::RowIndexAt(uint32_t aCellIdx)
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ return -1;
+
+ int32_t rowIdx = -1, colIdx = -1;
+ tableFrame->GetRowAndColumnByIndex(aCellIdx, &rowIdx, &colIdx);
+ return rowIdx;
+}
+
+void
+HTMLTableAccessible::RowAndColIndicesAt(uint32_t aCellIdx, int32_t* aRowIdx,
+ int32_t* aColIdx)
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (tableFrame)
+ tableFrame->GetRowAndColumnByIndex(aCellIdx, aRowIdx, aColIdx);
+}
+
+uint32_t
+HTMLTableAccessible::ColExtentAt(uint32_t aRowIdx, uint32_t aColIdx)
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ return 0;
+
+ return tableFrame->GetEffectiveColSpanAt(aRowIdx, aColIdx);
+}
+
+uint32_t
+HTMLTableAccessible::RowExtentAt(uint32_t aRowIdx, uint32_t aColIdx)
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ return 0;
+
+ return tableFrame->GetEffectiveRowSpanAt(aRowIdx, aColIdx);
+}
+
+bool
+HTMLTableAccessible::IsColSelected(uint32_t aColIdx)
+{
+ bool isSelected = false;
+
+ uint32_t rowCount = RowCount();
+ for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ isSelected = IsCellSelected(rowIdx, aColIdx);
+ if (!isSelected)
+ return false;
+ }
+
+ return isSelected;
+}
+
+bool
+HTMLTableAccessible::IsRowSelected(uint32_t aRowIdx)
+{
+ bool isSelected = false;
+
+ uint32_t colCount = ColCount();
+ for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
+ isSelected = IsCellSelected(aRowIdx, colIdx);
+ if (!isSelected)
+ return false;
+ }
+
+ return isSelected;
+}
+
+bool
+HTMLTableAccessible::IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx)
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ return false;
+
+ nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(aRowIdx, aColIdx);
+ return cellFrame ? cellFrame->IsSelected() : false;
+}
+
+void
+HTMLTableAccessible::SelectRow(uint32_t aRowIdx)
+{
+ DebugOnly<nsresult> rv =
+ RemoveRowsOrColumnsFromSelection(aRowIdx,
+ nsISelectionPrivate::TABLESELECTION_ROW,
+ true);
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "RemoveRowsOrColumnsFromSelection() Shouldn't fail!");
+
+ AddRowOrColumnToSelection(aRowIdx, nsISelectionPrivate::TABLESELECTION_ROW);
+}
+
+void
+HTMLTableAccessible::SelectCol(uint32_t aColIdx)
+{
+ DebugOnly<nsresult> rv =
+ RemoveRowsOrColumnsFromSelection(aColIdx,
+ nsISelectionPrivate::TABLESELECTION_COLUMN,
+ true);
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "RemoveRowsOrColumnsFromSelection() Shouldn't fail!");
+
+ AddRowOrColumnToSelection(aColIdx, nsISelectionPrivate::TABLESELECTION_COLUMN);
+}
+
+void
+HTMLTableAccessible::UnselectRow(uint32_t aRowIdx)
+{
+ RemoveRowsOrColumnsFromSelection(aRowIdx,
+ nsISelectionPrivate::TABLESELECTION_ROW,
+ false);
+}
+
+void
+HTMLTableAccessible::UnselectCol(uint32_t aColIdx)
+{
+ RemoveRowsOrColumnsFromSelection(aColIdx,
+ nsISelectionPrivate::TABLESELECTION_COLUMN,
+ false);
+}
+
+nsresult
+HTMLTableAccessible::AddRowOrColumnToSelection(int32_t aIndex, uint32_t aTarget)
+{
+ bool doSelectRow = (aTarget == nsISelectionPrivate::TABLESELECTION_ROW);
+
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ return NS_OK;
+
+ uint32_t count = 0;
+ if (doSelectRow)
+ count = ColCount();
+ else
+ count = RowCount();
+
+ nsIPresShell* presShell(mDoc->PresShell());
+ RefPtr<nsFrameSelection> tableSelection =
+ const_cast<nsFrameSelection*>(presShell->ConstFrameSelection());
+
+ for (uint32_t idx = 0; idx < count; idx++) {
+ int32_t rowIdx = doSelectRow ? aIndex : idx;
+ int32_t colIdx = doSelectRow ? idx : aIndex;
+ nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx);
+ if (cellFrame && !cellFrame->IsSelected()) {
+ nsresult rv = tableSelection->SelectCellElement(cellFrame->GetContent());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+HTMLTableAccessible::RemoveRowsOrColumnsFromSelection(int32_t aIndex,
+ uint32_t aTarget,
+ bool aIsOuter)
+{
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ return NS_OK;
+
+ nsIPresShell* presShell(mDoc->PresShell());
+ RefPtr<nsFrameSelection> tableSelection =
+ const_cast<nsFrameSelection*>(presShell->ConstFrameSelection());
+
+ bool doUnselectRow = (aTarget == nsISelectionPrivate::TABLESELECTION_ROW);
+ uint32_t count = doUnselectRow ? ColCount() : RowCount();
+
+ int32_t startRowIdx = doUnselectRow ? aIndex : 0;
+ int32_t endRowIdx = doUnselectRow ? aIndex : count - 1;
+ int32_t startColIdx = doUnselectRow ? 0 : aIndex;
+ int32_t endColIdx = doUnselectRow ? count - 1 : aIndex;
+
+ if (aIsOuter)
+ return tableSelection->RestrictCellsToSelection(mContent,
+ startRowIdx, startColIdx,
+ endRowIdx, endColIdx);
+
+ return tableSelection->RemoveCellsFromSelection(mContent,
+ startRowIdx, startColIdx,
+ endRowIdx, endColIdx);
+}
+
+void
+HTMLTableAccessible::Description(nsString& aDescription)
+{
+ // Helpful for debugging layout vs. data tables
+ aDescription.Truncate();
+ Accessible::Description(aDescription);
+ if (!aDescription.IsEmpty())
+ return;
+
+ // Use summary as description if it weren't used as a name.
+ // XXX: get rid code duplication with NameInternal().
+ Accessible* caption = Caption();
+ if (caption) {
+ nsIContent* captionContent = caption->GetContent();
+ if (captionContent) {
+ nsAutoString captionText;
+ nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent,
+ &captionText);
+
+ if (!captionText.IsEmpty()) { // summary isn't used as a name.
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::summary,
+ aDescription);
+ }
+ }
+ }
+
+#ifdef SHOW_LAYOUT_HEURISTIC
+ if (aDescription.IsEmpty()) {
+ bool isProbablyForLayout = IsProbablyLayoutTable();
+ aDescription = mLayoutHeuristic;
+ }
+ printf("\nTABLE: %s\n", NS_ConvertUTF16toUTF8(mLayoutHeuristic).get());
+#endif
+}
+
+bool
+HTMLTableAccessible::HasDescendant(const nsAString& aTagName, bool aAllowEmpty)
+{
+ nsCOMPtr<nsIHTMLCollection> elements =
+ mContent->AsElement()->GetElementsByTagName(aTagName);
+
+ Element* foundItem = elements->Item(0);
+ if (!foundItem)
+ return false;
+
+ if (aAllowEmpty)
+ return true;
+
+ // Make sure that the item we found has contents and either has multiple
+ // children or the found item is not a whitespace-only text node.
+ if (foundItem->GetChildCount() > 1)
+ return true; // Treat multiple child nodes as non-empty
+
+ nsIContent *innerItemContent = foundItem->GetFirstChild();
+ if (innerItemContent && !innerItemContent->TextIsOnlyWhitespace())
+ return true;
+
+ // If we found more than one node then return true not depending on
+ // aAllowEmpty flag.
+ // XXX it might be dummy but bug 501375 where we changed this addresses
+ // performance problems only. Note, currently 'aAllowEmpty' flag is used for
+ // caption element only. On another hand we create accessible object for
+ // the first entry of caption element (see
+ // HTMLTableAccessible::InsertChildAt).
+ return !!elements->Item(1);
+}
+
+bool
+HTMLTableAccessible::IsProbablyLayoutTable()
+{
+ // Implement a heuristic to determine if table is most likely used for layout
+ // XXX do we want to look for rowspan or colspan, especialy that span all but a couple cells
+ // at the beginning or end of a row/col, and especially when they occur at the edge of a table?
+ // XXX expose this info via object attributes to AT-SPI
+
+ // XXX For now debugging descriptions are always on via SHOW_LAYOUT_HEURISTIC
+ // This will allow release trunk builds to be used by testers to refine the algorithm
+ // Change to |#define SHOW_LAYOUT_HEURISTIC DEBUG| before final release
+#ifdef SHOW_LAYOUT_HEURISTIC
+#define RETURN_LAYOUT_ANSWER(isLayout, heuristic) \
+ { \
+ mLayoutHeuristic = isLayout ? \
+ NS_LITERAL_STRING("layout table: " heuristic) : \
+ NS_LITERAL_STRING("data table: " heuristic); \
+ return isLayout; \
+ }
+#else
+#define RETURN_LAYOUT_ANSWER(isLayout, heuristic) { return isLayout; }
+#endif
+
+ DocAccessible* docAccessible = Document();
+ if (docAccessible) {
+ uint64_t docState = docAccessible->State();
+ if (docState & states::EDITABLE) { // Need to see all elements while document is being edited
+ RETURN_LAYOUT_ANSWER(false, "In editable document");
+ }
+ }
+
+ // Check to see if an ARIA role overrides the role from native markup,
+ // but for which we still expose table semantics (treegrid, for example).
+ if (Role() != roles::TABLE)
+ RETURN_LAYOUT_ANSWER(false, "Has role attribute");
+
+ if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::role)) {
+ // Role attribute is present, but overridden roles have already been dealt with.
+ // Only landmarks and other roles that don't override the role from native
+ // markup are left to deal with here.
+ RETURN_LAYOUT_ANSWER(false, "Has role attribute, weak role, and role is table");
+ }
+
+ NS_ASSERTION(mContent->IsHTMLElement(nsGkAtoms::table),
+ "table should not be built by CSS display:table style");
+
+ // Check if datatable attribute has "0" value.
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::datatable,
+ NS_LITERAL_STRING("0"), eCaseMatters)) {
+ RETURN_LAYOUT_ANSWER(true, "Has datatable = 0 attribute, it's for layout");
+ }
+
+ // Check for legitimate data table attributes.
+ nsAutoString summary;
+ if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::summary, summary) &&
+ !summary.IsEmpty())
+ RETURN_LAYOUT_ANSWER(false, "Has summary -- legitimate table structures");
+
+ // Check for legitimate data table elements.
+ Accessible* caption = FirstChild();
+ if (caption && caption->Role() == roles::CAPTION && caption->HasChildren())
+ RETURN_LAYOUT_ANSWER(false, "Not empty caption -- legitimate table structures");
+
+ for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
+ childElm = childElm->GetNextSibling()) {
+ if (!childElm->IsHTMLElement())
+ continue;
+
+ if (childElm->IsAnyOfHTMLElements(nsGkAtoms::col,
+ nsGkAtoms::colgroup,
+ nsGkAtoms::tfoot,
+ nsGkAtoms::thead)) {
+ RETURN_LAYOUT_ANSWER(false,
+ "Has col, colgroup, tfoot or thead -- legitimate table structures");
+ }
+
+ if (childElm->IsHTMLElement(nsGkAtoms::tbody)) {
+ for (nsIContent* rowElm = childElm->GetFirstChild(); rowElm;
+ rowElm = rowElm->GetNextSibling()) {
+ if (rowElm->IsHTMLElement(nsGkAtoms::tr)) {
+ for (nsIContent* cellElm = rowElm->GetFirstChild(); cellElm;
+ cellElm = cellElm->GetNextSibling()) {
+ if (cellElm->IsHTMLElement()) {
+
+ if (cellElm->NodeInfo()->Equals(nsGkAtoms::th)) {
+ RETURN_LAYOUT_ANSWER(false,
+ "Has th -- legitimate table structures");
+ }
+
+ if (cellElm->HasAttr(kNameSpaceID_None, nsGkAtoms::headers) ||
+ cellElm->HasAttr(kNameSpaceID_None, nsGkAtoms::scope) ||
+ cellElm->HasAttr(kNameSpaceID_None, nsGkAtoms::abbr)) {
+ RETURN_LAYOUT_ANSWER(false,
+ "Has headers, scope, or abbr attribute -- legitimate table structures");
+ }
+
+ Accessible* cell = mDoc->GetAccessible(cellElm);
+ if (cell && cell->ChildCount() == 1 &&
+ cell->FirstChild()->IsAbbreviation()) {
+ RETURN_LAYOUT_ANSWER(false,
+ "has abbr -- legitimate table structures");
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (HasDescendant(NS_LITERAL_STRING("table"))) {
+ RETURN_LAYOUT_ANSWER(true, "Has a nested table within it");
+ }
+
+ // If only 1 column or only 1 row, it's for layout
+ uint32_t colCount = ColCount();
+ if (colCount <=1) {
+ RETURN_LAYOUT_ANSWER(true, "Has only 1 column");
+ }
+ uint32_t rowCount = RowCount();
+ if (rowCount <=1) {
+ RETURN_LAYOUT_ANSWER(true, "Has only 1 row");
+ }
+
+ // Check for many columns
+ if (colCount >= 5) {
+ RETURN_LAYOUT_ANSWER(false, ">=5 columns");
+ }
+
+ // Now we know there are 2-4 columns and 2 or more rows
+ // Check to see if there are visible borders on the cells
+ // XXX currently, we just check the first cell -- do we really need to do more?
+ nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (!tableFrame)
+ RETURN_LAYOUT_ANSWER(false, "table with no frame!");
+
+ nsIFrame* cellFrame = tableFrame->GetCellFrameAt(0, 0);
+ if (!cellFrame)
+ RETURN_LAYOUT_ANSWER(false, "table's first cell has no frame!");
+
+ nsMargin border;
+ cellFrame->GetXULBorder(border);
+ if (border.top && border.bottom && border.left && border.right) {
+ RETURN_LAYOUT_ANSWER(false, "Has nonzero border-width on table cell");
+ }
+
+ /**
+ * Rules for non-bordered tables with 2-4 columns and 2+ rows from here on forward
+ */
+
+ // Check for styled background color across rows (alternating background
+ // color is a common feature for data tables).
+ uint32_t childCount = ChildCount();
+ nscolor rowColor = 0;
+ nscolor prevRowColor;
+ for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
+ Accessible* child = GetChildAt(childIdx);
+ if (child->Role() == roles::ROW) {
+ prevRowColor = rowColor;
+ nsIFrame* rowFrame = child->GetFrame();
+ rowColor = rowFrame->StyleBackground()->mBackgroundColor;
+
+ if (childIdx > 0 && prevRowColor != rowColor)
+ RETURN_LAYOUT_ANSWER(false, "2 styles of row background color, non-bordered");
+ }
+ }
+
+ // Check for many rows
+ const uint32_t kMaxLayoutRows = 20;
+ if (rowCount > kMaxLayoutRows) { // A ton of rows, this is probably for data
+ RETURN_LAYOUT_ANSWER(false, ">= kMaxLayoutRows (20) and non-bordered");
+ }
+
+ // Check for very wide table.
+ nsIFrame* documentFrame = Document()->GetFrame();
+ nsSize documentSize = documentFrame->GetSize();
+ if (documentSize.width > 0) {
+ nsSize tableSize = GetFrame()->GetSize();
+ int32_t percentageOfDocWidth = (100 * tableSize.width) / documentSize.width;
+ if (percentageOfDocWidth > 95) {
+ // 3-4 columns, no borders, not a lot of rows, and 95% of the doc's width
+ // Probably for layout
+ RETURN_LAYOUT_ANSWER(true,
+ "<= 4 columns, table width is 95% of document width");
+ }
+ }
+
+ // Two column rules
+ if (rowCount * colCount <= 10) {
+ RETURN_LAYOUT_ANSWER(true, "2-4 columns, 10 cells or less, non-bordered");
+ }
+
+ if (HasDescendant(NS_LITERAL_STRING("embed")) ||
+ HasDescendant(NS_LITERAL_STRING("object")) ||
+ HasDescendant(NS_LITERAL_STRING("applet")) ||
+ HasDescendant(NS_LITERAL_STRING("iframe"))) {
+ RETURN_LAYOUT_ANSWER(true, "Has no borders, and has iframe, object, applet or iframe, typical of advertisements");
+ }
+
+ RETURN_LAYOUT_ANSWER(false, "no layout factor strong enough, so will guess data");
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLCaptionAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+Relation
+HTMLCaptionAccessible::RelationByType(RelationType aType)
+{
+ Relation rel = HyperTextAccessible::RelationByType(aType);
+ if (aType == RelationType::LABEL_FOR)
+ rel.AppendTarget(Parent());
+
+ return rel;
+}
+
+role
+HTMLCaptionAccessible::NativeRole()
+{
+ return roles::CAPTION;
+}
diff --git a/accessible/html/HTMLTableAccessible.h b/accessible/html/HTMLTableAccessible.h
new file mode 100644
index 000000000..830d34ae9
--- /dev/null
+++ b/accessible/html/HTMLTableAccessible.h
@@ -0,0 +1,226 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_HTMLTableAccessible_h__
+#define mozilla_a11y_HTMLTableAccessible_h__
+
+#include "HyperTextAccessibleWrap.h"
+#include "TableAccessible.h"
+#include "TableCellAccessible.h"
+
+class nsITableCellLayout;
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * HTML table cell accessible (html:td).
+ */
+class HTMLTableCellAccessible : public HyperTextAccessibleWrap,
+ public TableCellAccessible
+{
+public:
+ HTMLTableCellAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual TableCellAccessible* AsTableCell() override { return this; }
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+ virtual uint64_t NativeInteractiveState() const override;
+ virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override;
+ virtual mozilla::a11y::GroupPos GroupPosition() override;
+
+ // TableCellAccessible
+ virtual TableAccessible* Table() const override;
+ virtual uint32_t ColIdx() const override;
+ virtual uint32_t RowIdx() const override;
+ virtual uint32_t ColExtent() const override;
+ virtual uint32_t RowExtent() const override;
+ virtual void ColHeaderCells(nsTArray<Accessible*>* aCells) override;
+ virtual void RowHeaderCells(nsTArray<Accessible*>* aCells) override;
+ virtual bool Selected() override;
+
+protected:
+ virtual ~HTMLTableCellAccessible() {}
+
+ /**
+ * Return nsITableCellLayout of the table cell frame.
+ */
+ nsITableCellLayout* GetCellLayout() const;
+
+ /**
+ * Return row and column indices of the cell.
+ */
+ nsresult GetCellIndexes(int32_t& aRowIdx, int32_t& aColIdx) const;
+};
+
+
+/**
+ * HTML table row/column header accessible (html:th or html:td@scope).
+ */
+class HTMLTableHeaderCellAccessible : public HTMLTableCellAccessible
+{
+public:
+ HTMLTableHeaderCellAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+};
+
+
+/**
+ * HTML table row accessible (html:tr).
+ */
+class HTMLTableRowAccessible : public AccessibleWrap
+{
+public:
+ HTMLTableRowAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+ {
+ mType = eHTMLTableRowType;
+ mGenericTypes |= eTableRow;
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+ virtual mozilla::a11y::GroupPos GroupPosition() override;
+
+protected:
+ virtual ~HTMLTableRowAccessible() { }
+};
+
+
+/**
+ * HTML table accessible (html:table).
+ */
+
+// To turn on table debugging descriptions define SHOW_LAYOUT_HEURISTIC
+// This allow release trunk builds to be used by testers to refine the
+// data vs. layout heuristic
+// #define SHOW_LAYOUT_HEURISTIC
+
+class HTMLTableAccessible : public AccessibleWrap,
+ public TableAccessible
+{
+public:
+ HTMLTableAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+ {
+ mType = eHTMLTableType;
+ mGenericTypes |= eTable;
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // TableAccessible
+ virtual Accessible* Caption() const override;
+ virtual void Summary(nsString& aSummary) override;
+ virtual uint32_t ColCount() override;
+ virtual uint32_t RowCount() override;
+ virtual Accessible* CellAt(uint32_t aRowIndex, uint32_t aColumnIndex) override;
+ virtual int32_t CellIndexAt(uint32_t aRowIdx, uint32_t aColIdx) override;
+ virtual int32_t ColIndexAt(uint32_t aCellIdx) override;
+ virtual int32_t RowIndexAt(uint32_t aCellIdx) override;
+ virtual void RowAndColIndicesAt(uint32_t aCellIdx, int32_t* aRowIdx,
+ int32_t* aColIdx) override;
+ virtual uint32_t ColExtentAt(uint32_t aRowIdx, uint32_t aColIdx) override;
+ virtual uint32_t RowExtentAt(uint32_t aRowIdx, uint32_t aColIdx) override;
+ virtual bool IsColSelected(uint32_t aColIdx) override;
+ virtual bool IsRowSelected(uint32_t aRowIdx) override;
+ virtual bool IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) override;
+ virtual uint32_t SelectedCellCount() override;
+ virtual uint32_t SelectedColCount() override;
+ virtual uint32_t SelectedRowCount() override;
+ virtual void SelectedCells(nsTArray<Accessible*>* aCells) override;
+ virtual void SelectedCellIndices(nsTArray<uint32_t>* aCells) override;
+ virtual void SelectedColIndices(nsTArray<uint32_t>* aCols) override;
+ virtual void SelectedRowIndices(nsTArray<uint32_t>* aRows) override;
+ virtual void SelectCol(uint32_t aColIdx) override;
+ virtual void SelectRow(uint32_t aRowIdx) override;
+ virtual void UnselectCol(uint32_t aColIdx) override;
+ virtual void UnselectRow(uint32_t aRowIdx) override;
+ virtual bool IsProbablyLayoutTable() override;
+ virtual Accessible* AsAccessible() override { return this; }
+
+ // Accessible
+ virtual TableAccessible* AsTable() override { return this; }
+ virtual void Description(nsString& aDescription) override;
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+ virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override;
+ virtual Relation RelationByType(RelationType aRelationType) override;
+
+ virtual bool InsertChildAt(uint32_t aIndex, Accessible* aChild) override;
+
+protected:
+ virtual ~HTMLTableAccessible() {}
+
+ // Accessible
+ virtual ENameValueFlag NativeName(nsString& aName) override;
+
+ // HTMLTableAccessible
+
+ /**
+ * Add row or column to selection.
+ *
+ * @param aIndex [in] index of row or column to be selected
+ * @param aTarget [in] indicates what should be selected, either row or column
+ * (see nsISelectionPrivate)
+ */
+ nsresult AddRowOrColumnToSelection(int32_t aIndex, uint32_t aTarget);
+
+ /**
+ * Removes rows or columns at the given index or outside it from selection.
+ *
+ * @param aIndex [in] row or column index
+ * @param aTarget [in] indicates whether row or column should unselected
+ * @param aIsOuter [in] indicates whether all rows or column excepting
+ * the given one should be unselected or the given one
+ * should be unselected only
+ */
+ nsresult RemoveRowsOrColumnsFromSelection(int32_t aIndex,
+ uint32_t aTarget,
+ bool aIsOuter);
+
+ /**
+ * Return true if table has an element with the given tag name.
+ *
+ * @param aTagName [in] tag name of searched element
+ * @param aAllowEmpty [in, optional] points if found element can be empty
+ * or contain whitespace text only.
+ */
+ bool HasDescendant(const nsAString& aTagName, bool aAllowEmpty = true);
+
+#ifdef SHOW_LAYOUT_HEURISTIC
+ nsString mLayoutHeuristic;
+#endif
+};
+
+/**
+ * HTML caption accessible (html:caption).
+ */
+class HTMLCaptionAccessible : public HyperTextAccessibleWrap
+{
+public:
+ HTMLCaptionAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HyperTextAccessibleWrap(aContent, aDoc) { mType = eHTMLCaptionType; }
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+ virtual Relation RelationByType(RelationType aRelationType) override;
+
+protected:
+ virtual ~HTMLCaptionAccessible() { }
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/html/moz.build b/accessible/html/moz.build
new file mode 100644
index 000000000..8d92332cd
--- /dev/null
+++ b/accessible/html/moz.build
@@ -0,0 +1,50 @@
+# -*- 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/.
+
+UNIFIED_SOURCES += [
+ 'HTMLCanvasAccessible.cpp',
+ 'HTMLElementAccessibles.cpp',
+ 'HTMLFormControlAccessible.cpp',
+ 'HTMLImageMapAccessible.cpp',
+ 'HTMLLinkAccessible.cpp',
+ 'HTMLListAccessible.cpp',
+ 'HTMLSelectAccessible.cpp',
+ 'HTMLTableAccessible.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/accessible/base',
+ '/accessible/generic',
+ '/accessible/xpcom',
+ '/layout/generic',
+ '/layout/tables',
+ '/layout/xul',
+]
+
+if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ LOCAL_INCLUDES += [
+ '/accessible/atk',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ LOCAL_INCLUDES += [
+ '/accessible/windows/ia2',
+ '/accessible/windows/msaa',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ LOCAL_INCLUDES += [
+ '/accessible/mac',
+ ]
+else:
+ LOCAL_INCLUDES += [
+ '/accessible/other',
+ ]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/accessible/interfaces/ia2/IA2Marshal.def b/accessible/interfaces/ia2/IA2Marshal.def
new file mode 100644
index 000000000..0040d3b02
--- /dev/null
+++ b/accessible/interfaces/ia2/IA2Marshal.def
@@ -0,0 +1,11 @@
+;+# 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/.
+
+LIBRARY IA2Marshal.dll
+EXPORTS DllGetClassObject PRIVATE
+ DllCanUnloadNow PRIVATE
+ DllRegisterServer PRIVATE
+ DllUnregisterServer PRIVATE
+ GetProxyDllInfo PRIVATE
+
diff --git a/accessible/interfaces/ia2/IA2Marshal.dll.manifest b/accessible/interfaces/ia2/IA2Marshal.dll.manifest
new file mode 100644
index 000000000..f3d8059d5
--- /dev/null
+++ b/accessible/interfaces/ia2/IA2Marshal.dll.manifest
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <assemblyIdentity type="win32" name="IA2Marshal" version="1.0.0.0" />
+ <file name="IA2Marshal.dll">
+ <comInterfaceProxyStub
+ iid="{E89F726E-C4F4-4c19-BB19-B647D7FA8478}"
+ proxyStubClsid32="{E89F726E-C4F4-4c19-BB19-B647D7FA8478}"
+ name="IAccessible2"
+ tlbid="{CE3F726E-D1D3-44FE-B995-FF1DB3B48B2B}"
+ />
+ </file>
+</assembly>
diff --git a/accessible/interfaces/ia2/IA2Marshal.rc b/accessible/interfaces/ia2/IA2Marshal.rc
new file mode 100644
index 000000000..b120abce7
--- /dev/null
+++ b/accessible/interfaces/ia2/IA2Marshal.rc
@@ -0,0 +1,5 @@
+/* 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/. */
+
+1 typelib IA2Typelib.tlb
diff --git a/accessible/interfaces/ia2/IA2Typelib.idl b/accessible/interfaces/ia2/IA2Typelib.idl
new file mode 100644
index 000000000..c9e4aa7d1
--- /dev/null
+++ b/accessible/interfaces/ia2/IA2Typelib.idl
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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/. */
+
+typedef struct _MozRemotableHandle
+{
+ long fContext;
+ long hRemote;
+} MozRemotableHandle;
+
+typedef [unique] MozRemotableHandle * mozHWND;
+typedef [unique] MozRemotableHandle * mozHMENU;
+typedef [unique] MozRemotableHandle * mozHACCEL;
+typedef [unique] MozRemotableHandle * mozHBRUSH;
+typedef [unique] MozRemotableHandle * mozHFONT;
+typedef [unique] MozRemotableHandle * mozHDC;
+typedef [unique] MozRemotableHandle * mozHICON;
+typedef [unique] MozRemotableHandle * mozHRGN;
+typedef [unique] MozRemotableHandle * mozHMONITOR;
+
+cpp_quote("#if 0")
+typedef [wire_marshal(mozHWND)] void* HWND;
+typedef [wire_marshal(mozHMENU)] void* HMENU;
+typedef [wire_marshal(mozHACCEL)] void* HACCEL;
+typedef [wire_marshal(mozHBRUSH)] void* HBRUSH;
+typedef [wire_marshal(mozHFONT)] void* HFONT;
+typedef [wire_marshal(mozHDC)] void* HDC;
+typedef [wire_marshal(mozHICON)] void* HICON;
+typedef [wire_marshal(mozHRGN)] void* HRGN;
+typedef [wire_marshal(mozHMONITOR)] void* HMONITOR;
+cpp_quote("#endif // 0")
+
+import "Accessible2.idl";
+import "Accessible2_2.idl";
+import "Accessible2_3.idl";
+import "AccessibleAction.idl";
+import "AccessibleApplication.idl";
+import "AccessibleComponent.idl";
+import "AccessibleDocument.idl";
+import "AccessibleEditableText.idl";
+import "AccessibleEventId.idl";
+import "AccessibleHyperlink.idl";
+import "AccessibleHypertext.idl";
+import "AccessibleHypertext2.idl";
+import "AccessibleImage.idl";
+import "AccessibleRelation.idl";
+import "AccessibleRole.idl";
+import "AccessibleStates.idl";
+import "AccessibleTable.idl";
+import "AccessibleTable2.idl";
+import "AccessibleTableCell.idl";
+import "AccessibleText.idl";
+import "AccessibleText2.idl";
+import "AccessibleValue.idl";
+import "IA2CommonTypes.idl";
+
+// We are explicitly using #include instead of import so that the imported
+// IDL is treated as part of this IDL file.
+#include "IA2TypeLibrary.idl"
diff --git a/accessible/interfaces/ia2/Makefile.in b/accessible/interfaces/ia2/Makefile.in
new file mode 100644
index 000000000..36110d08f
--- /dev/null
+++ b/accessible/interfaces/ia2/Makefile.in
@@ -0,0 +1,108 @@
+# 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/.
+
+IA2DIR = $(topsrcdir)/other-licenses/ia2
+
+GARBAGE += $(MIDL_GENERATED_FILES) \
+ $(MIDL_UNUSED_GENERATED_FILES) \
+ $(NULL)
+
+# Please keep this list in sync with the moz.build file until the rest of this
+# Makefile is ported over.
+MIDL_INTERFACES = \
+ Accessible2.idl \
+ Accessible2_2.idl \
+ Accessible2_3.idl \
+ AccessibleAction.idl \
+ AccessibleApplication.idl \
+ AccessibleComponent.idl \
+ AccessibleDocument.idl \
+ AccessibleEditableText.idl \
+ AccessibleHyperlink.idl \
+ AccessibleHypertext.idl \
+ AccessibleHypertext2.idl \
+ AccessibleImage.idl \
+ AccessibleRelation.idl \
+ AccessibleTable.idl \
+ AccessibleTable2.idl \
+ AccessibleTableCell.idl \
+ AccessibleText.idl \
+ AccessibleText2.idl \
+ AccessibleValue.idl \
+ $(NULL)
+
+# Please keep this list in sync with the moz.build file until the rest of this
+# Makefile is ported over.
+MIDL_ENUMS = \
+ AccessibleEventId.idl \
+ AccessibleRole.idl \
+ AccessibleStates.idl \
+ IA2CommonTypes.idl \
+ $(NULL)
+
+MIDL_LIBRARIES = \
+ IA2Typelib.idl \
+ $(NULL)
+
+CSRCS = \
+ dlldata.c \
+ $(MIDL_INTERFACES:%.idl=%_p.c) \
+ $(MIDL_INTERFACES:%.idl=%_i.c) \
+ $(NULL)
+
+MIDL_GENERATED_FILES = \
+ dlldata.c \
+ $(MIDL_INTERFACES:%.idl=%_p.c) \
+ $(MIDL_INTERFACES:%.idl=%_i.c) \
+ $(MIDL_INTERFACES:%.idl=%.h) \
+ $(MIDL_ENUMS:%.idl=%.h) \
+ $(NULL)
+
+# We want to generate a .tlb from MIDL_LIBRARIES, but midl also generates
+# a bunch of .h and .c files that we're not interested in.
+MIDL_UNUSED_GENERATED_FILES = \
+ $(MIDL_LIBRARIES:%.idl=%_p.c) \
+ $(MIDL_LIBRARIES:%.idl=%_i.c) \
+ $(MIDL_LIBRARIES:%.idl=%.h) \
+ $(MIDL_LIBRARIES:%.idl=%.c) \
+ $(NULL)
+
+EMBED_MANIFEST_AT = 2
+
+INSTALL_TARGETS += midl
+midl_FILES := $(filter %.h %_i.c,$(MIDL_GENERATED_FILES))
+midl_DEST = $(DIST)/include
+midl_TARGET := export
+
+include $(topsrcdir)/config/rules.mk
+
+# generate list of to-be-generated files that are missing
+# but ignore special file dlldata.c
+missing:=$(strip $(foreach onefile,$(strip $(subst dlldata.c,,$(MIDL_GENERATED_FILES))),$(if $(wildcard $(onefile)),,$(onefile))))
+
+missing_base:=$(sort $(basename $(subst _p.c,,$(subst _i.c,,$(missing)))))
+
+$(MIDL_GENERATED_FILES) : midl_done typelib_done
+
+ifneq ("$(missing)","")
+midl_done : FORCE
+endif
+
+midl_done : $(addprefix $(IA2DIR)/,$(MIDL_INTERFACES) $(MIDL_ENUMS))
+ for idl in $(sort $(subst FORCE,,$?) $(addsuffix .idl,$(addprefix $(IA2DIR)/,$(missing_base)))); do \
+ $(MIDL) $(MIDL_FLAGS) -app_config -I $(IA2DIR) -Oicf $$idl; \
+ done
+ touch $@
+
+# The intent of this rule is to generate the .tlb file that is referenced in the
+# .rc file for IA2Marshal.dll
+typelib_done : $(MIDL_LIBRARIES)
+ for idl in $?; do \
+ $(MIDL) $(MIDL_FLAGS) -app_config -I $(IA2DIR) -D _MIDL_DECLARE_WIREM_HANDLE -dlldata `basename $$idl .idl`.c -Oicf $$idl; \
+ done
+ touch $@
+
+# This marshall dll is NOT registered in the installer (agreed to by IA2 participants)
+register::
+ regsvr32 -s $(DIST)/bin/$(SHARED_LIBRARY)
diff --git a/accessible/interfaces/ia2/moz.build b/accessible/interfaces/ia2/moz.build
new file mode 100644
index 000000000..430e74a6e
--- /dev/null
+++ b/accessible/interfaces/ia2/moz.build
@@ -0,0 +1,33 @@
+# -*- 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/.
+
+GeckoSharedLibrary('IA2Marshal', linkage=None)
+
+DEFINES['REGISTER_PROXY_DLL'] = True
+
+DEFFILE = SRCDIR + '/IA2Marshal.def'
+
+OS_LIBS += [
+ 'uuid',
+ 'kernel32',
+ 'rpcrt4',
+ 'ole32',
+ 'oleaut32',
+]
+
+GENERATED_FILES += [
+ 'IA2Typelib.tlb',
+]
+
+RCINCLUDE = 'IA2Marshal.rc'
+
+# The Windows MIDL code generator creates things like:
+#
+# #endif !_MIDL_USE_GUIDDEF_
+#
+# which clang-cl complains about. MSVC doesn't, so turn this warning off.
+if CONFIG['CLANG_CL']:
+ CXXFLAGS += ['-Wno-extra-tokens']
diff --git a/accessible/interfaces/moz.build b/accessible/interfaces/moz.build
new file mode 100644
index 000000000..f761f5f9e
--- /dev/null
+++ b/accessible/interfaces/moz.build
@@ -0,0 +1,40 @@
+# -*- 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/.
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows' and CONFIG['COMPILE_ENVIRONMENT']:
+ DIRS += ['msaa', 'ia2']
+
+XPIDL_SOURCES += [
+ 'nsIAccessibilityService.idl',
+ 'nsIAccessible.idl',
+ 'nsIAccessibleApplication.idl',
+ 'nsIAccessibleCaretMoveEvent.idl',
+ 'nsIAccessibleDocument.idl',
+ 'nsIAccessibleEditableText.idl',
+ 'nsIAccessibleEvent.idl',
+ 'nsIAccessibleHideEvent.idl',
+ 'nsIAccessibleHyperLink.idl',
+ 'nsIAccessibleHyperText.idl',
+ 'nsIAccessibleImage.idl',
+ 'nsIAccessibleObjectAttributeChangedEvent.idl',
+ 'nsIAccessiblePivot.idl',
+ 'nsIAccessibleRelation.idl',
+ 'nsIAccessibleRole.idl',
+ 'nsIAccessibleSelectable.idl',
+ 'nsIAccessibleStateChangeEvent.idl',
+ 'nsIAccessibleStates.idl',
+ 'nsIAccessibleTable.idl',
+ 'nsIAccessibleTableChangeEvent.idl',
+ 'nsIAccessibleText.idl',
+ 'nsIAccessibleTextChangeEvent.idl',
+ 'nsIAccessibleTextRange.idl',
+ 'nsIAccessibleTypes.idl',
+ 'nsIAccessibleValue.idl',
+ 'nsIAccessibleVirtualCursorChangeEvent.idl',
+ 'nsIXBLAccessible.idl',
+]
+
+XPIDL_MODULE = 'accessibility'
diff --git a/accessible/interfaces/msaa/AccessibleMarshal.def b/accessible/interfaces/msaa/AccessibleMarshal.def
new file mode 100644
index 000000000..a4992db1d
--- /dev/null
+++ b/accessible/interfaces/msaa/AccessibleMarshal.def
@@ -0,0 +1,11 @@
+;+# 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/.
+
+LIBRARY AccessibleMarshal.dll
+
+EXPORTS DllGetClassObject PRIVATE
+ DllCanUnloadNow PRIVATE
+ DllRegisterServer PRIVATE
+ DllUnregisterServer PRIVATE
+
diff --git a/accessible/interfaces/msaa/AccessibleMarshal.rc b/accessible/interfaces/msaa/AccessibleMarshal.rc
new file mode 100644
index 000000000..38d1cb41c
--- /dev/null
+++ b/accessible/interfaces/msaa/AccessibleMarshal.rc
@@ -0,0 +1,5 @@
+/* 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/. */
+
+1 typelib ISimpleDOMNode.tlb
diff --git a/accessible/interfaces/msaa/ISimpleDOMDocument.idl b/accessible/interfaces/msaa/ISimpleDOMDocument.idl
new file mode 100644
index 000000000..3797b9be0
--- /dev/null
+++ b/accessible/interfaces/msaa/ISimpleDOMDocument.idl
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+cpp_quote("///////////////////////////////////////////////////////////////////////////////////////////////////////")
+cpp_quote("//")
+cpp_quote("// ISimpleDOMDocument")
+cpp_quote("// ---------------------------------------------------------------------------------------------------=")
+cpp_quote("//")
+cpp_quote("// get_URL(out] BSTR *url)")
+cpp_quote("// ---------------------------------------------------------------------------------------------------=")
+cpp_quote("// Get the internet URL associated with this document.")
+cpp_quote("//")
+cpp_quote("// get_title([out BSTR *title")
+cpp_quote("// ---------------------------------------------------------------------------------------------------=")
+cpp_quote("// Get the document's title from the <TITLE> element")
+cpp_quote("//")
+cpp_quote("// get_mimeType([out BSTR *mimeType")
+cpp_quote("// ---------------------------------------------------------------------------------------------------=")
+cpp_quote("// Get the registered mime type, such as text/html")
+cpp_quote("//")
+cpp_quote("// get_docType([out] BSTR *docType")
+cpp_quote("// ---------------------------------------------------------------------------------------------------=")
+cpp_quote("// Get doctype associated with the <!DOCTYPE ..> element")
+cpp_quote("//")
+cpp_quote("// get_nameSpaceURIForID([in] short nameSpaceID, [out] BSTR *nameSpaceURI)")
+cpp_quote("// ---------------------------------------------------------------------------------------------------=")
+cpp_quote("// Some of the methods for ISimpleDOMNode return a nameSpaceID (-1,0,1,2,3,....)")
+cpp_quote("// This method returns the associated namespace URI for each ID.")
+cpp_quote("//")
+cpp_quote("// set_alternateViewMediaTypes([in] BSTR *commaSeparatedMediaType)")
+cpp_quote("// ---------------------------------------------------------------------------------------------------=")
+cpp_quote("// For style property retrieval on nsISimpleDOMNode elements, ")
+cpp_quote("// set the additional alternate media types that properties are available for.")
+cpp_quote("// [in] BSTR *commaSeparatedMediaTypes is a comma separate list, for example \"aural, braille\".")
+cpp_quote("// The alternate media properties are requested with nsISimpleDOMNode::get_computedStyle.")
+cpp_quote("// Note: setting this value on a document will increase memory overhead, and may create a small delay.")
+cpp_quote("//")
+cpp_quote("// W3C media Types:")
+cpp_quote("// * all: Suitable for all devices. ")
+cpp_quote("// * aural: Intended for speech synthesizers. See the section on aural style sheets for details. ")
+cpp_quote("// * braille: Intended for braille tactile feedback devices. ")
+cpp_quote("// * embossed: Intended for paged braille printers. ")
+cpp_quote("// * handheld: Intended for handheld devices - typically small screen, monochrome, limited bandwidth. ")
+cpp_quote("// * print: Intended for paged, opaque material and for documents viewed on screen in print preview mode. Please consult the section on paged media for information about formatting issues that are specific to paged media. ")
+cpp_quote("// * projection: Intended for projected presentations, for example projectors or print to transparencies. Please consult the section on paged media for information about formatting issues that are specific to paged media. ")
+cpp_quote("// * screen: Intended primarily for color computer screens. ")
+cpp_quote("// * tty: intended for media using a fixed-pitch character grid, such as teletypes, terminals, or portable devices with limited display capabilities. Authors should not use pixel units with the tty media type. ")
+cpp_quote("// * tv: Intended for television-type devices - low resolution, color, limited-scrollability screens, sound")
+cpp_quote("// * See latest W3C CSS specs for complete list of media types")
+cpp_quote("//")
+cpp_quote("//")
+cpp_quote("///////////////////////////////////////////////////////////////////////////////////////////////////////")
+cpp_quote("")
+cpp_quote("")
+
+import "objidl.idl";
+import "oaidl.idl";
+
+[object, uuid(0D68D6D0-D93D-4d08-A30D-F00DD1F45B24)]
+interface ISimpleDOMDocument : IUnknown
+{
+ [propget] HRESULT URL(
+ [out, retval] BSTR * url
+ );
+ [propget] HRESULT title(
+ [out, retval] BSTR * title
+ );
+ [propget] HRESULT mimeType(
+ [out, retval] BSTR * mimeType
+ );
+ [propget] HRESULT docType(
+ [out, retval] BSTR * docType
+ );
+ [propget] HRESULT nameSpaceURIForID(
+ [in] short nameSpaceID,
+ [out, retval] BSTR * nameSpaceURI
+ );
+ [propput] HRESULT alternateViewMediaTypes(
+ [in] BSTR * commaSeparatedMediaTypes
+ );
+}
diff --git a/accessible/interfaces/msaa/ISimpleDOMNode.idl b/accessible/interfaces/msaa/ISimpleDOMNode.idl
new file mode 100644
index 000000000..105cfbbc1
--- /dev/null
+++ b/accessible/interfaces/msaa/ISimpleDOMNode.idl
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+cpp_quote("///////////////////////////////////////////////////////////////////////////////////////////////////////")
+cpp_quote("//")
+cpp_quote("// ISimpleDOMNode")
+cpp_quote("// ---------------------------------------------------------------------------------------------------=")
+cpp_quote("// An interface that extends MSAA's IAccessible to provide readonly DOM node information via cross-process COM.")
+cpp_quote("//")
+cpp_quote("// get_nodeInfo(")
+cpp_quote("// /* [out] */ BSTR *nodeName, // For elements, this is the tag name")
+cpp_quote("// /* [out] */ short *nameSpaceID,")
+cpp_quote("// /* [out] */ BSTR *nodeValue, ")
+cpp_quote("// /* [out] */ unsigned int *numChildren); ")
+cpp_quote("// /* [out] */ unsigned int *uniqueID; // In Win32 accessible events we generate, the target's childID matches to this")
+cpp_quote("// /* [out] */ unsigned short *nodeType,")
+cpp_quote("// ---------------------------------------------------------------------------------------------------=")
+cpp_quote("// Get the basic information about a node.")
+cpp_quote("// The namespace ID can be mapped to an URI using nsISimpleDOMDocument::get_nameSpaceURIForID()")
+cpp_quote("//")
+cpp_quote("// get_attributes(")
+cpp_quote("// /* [in] */ unsigned short maxAttribs,")
+cpp_quote("// /* [out] */ unsigned short *numAttribs,")
+cpp_quote("// /* [out] */ BSTR *attribNames,")
+cpp_quote("// /* [out] */ short *nameSpaceID,")
+cpp_quote("// /* [out] */ BSTR *attribValues);")
+cpp_quote("// ---------------------------------------------------------------------------------------------------=")
+cpp_quote("// Returns 3 arrays - the attribute names and values, and a namespace ID for each")
+cpp_quote("// If the namespace ID is 0, it's the same namespace as the node's namespace")
+cpp_quote("//")
+cpp_quote("// get_attributesForNames(")
+cpp_quote("// /* [in] */ unsigned short numAttribs,")
+cpp_quote("// /* [in] */ BSTR *attribNames,")
+cpp_quote("// /* [in] */ short *nameSpaceID,")
+cpp_quote("// /* [out] */ BSTR *attribValues);")
+cpp_quote("// ---------------------------------------------------------------------------------------------------=")
+cpp_quote("// Takes 2 arrays - the attribute names and namespace IDs, and returns an array of corresponding values")
+cpp_quote("// If the namespace ID is 0, it's the same namespace as the node's namespace")
+cpp_quote("//")
+cpp_quote("// computedStyle( ")
+cpp_quote("// /* [in] */ unsigned short maxStyleProperties,")
+cpp_quote("// /* [out] */ unsigned short *numStyleProperties, ")
+cpp_quote("// /* [in] */ boolean useAlternateView, // If TRUE, returns properites for media as set in nsIDOMDocument::set_alternateViewMediaTypes")
+cpp_quote("// /* [out] */ BSTR *styleProperties, ")
+cpp_quote("// /* [out] */ BSTR *styleValues);")
+cpp_quote("// ---------------------------------------------------------------------------------------------------=")
+cpp_quote("// Returns 2 arrays -- the style properties and their values")
+cpp_quote("// useAlternateView=FALSE: gets properties for the default media type (usually screen)")
+cpp_quote("// useAlternateView=TRUE: properties for media types set w/ nsIDOMSimpleDocument::set_alternateViewMediaTypes()")
+cpp_quote("//")
+cpp_quote("// computedStyleForProperties( ")
+cpp_quote("// /* [in] */ unsigned short numStyleProperties, ")
+cpp_quote("// /* [in] */ boolean useAlternateView, // If TRUE, returns properites for media as set in nsIDOMDocument::set_alternateViewMediaTypes")
+cpp_quote("// /* [in] */ BSTR *styleProperties, ")
+cpp_quote("// /* [out] */ BSTR *styleValues);")
+cpp_quote("// ---------------------------------------------------------------------------------------------------=")
+cpp_quote("// Scroll the current view so that this dom node is visible.")
+cpp_quote("// placeTopLeft=TRUE: scroll until the top left corner of the dom node is at the top left corner of the view.")
+cpp_quote("// placeTopLeft=FALSE: scroll minimally to make the dom node visible. Don't scroll at all if already visible.")
+cpp_quote("//")
+cpp_quote("// scrollTo( ")
+cpp_quote("// /* [in] */ boolean placeTopLeft); ")
+cpp_quote("// ---------------------------------------------------------------------------------------------------=")
+cpp_quote("// Returns style property values for those properties in the styleProperties [in] array")
+cpp_quote("// Returns 2 arrays -- the style properties and their values")
+cpp_quote("// useAlternateView=FALSE: gets properties for the default media type (usually screen)")
+cpp_quote("// useAlternateView=TRUE: properties for media types set w/ nsIDOMSimpleDocument::set_alternateViewMediaTypes()")
+cpp_quote("//")
+cpp_quote("// get_parentNode (/* [out] */ ISimpleDOMNode **newNodePtr);")
+cpp_quote("// get_firstChild (/* [out] */ ISimpleDOMNode **newNodePtr);")
+cpp_quote("// get_lastChild (/* [out] */ ISimpleDOMNode **newNodePtr);")
+cpp_quote("// get_previousSibling(/* [out] */ ISimpleDOMNode **newNodePtr);")
+cpp_quote("// get_nextSibling (/* [out] */ ISimpleDOMNode **newNodePtr);")
+cpp_quote("// get_childAt (/* [in] */ unsigned childIndex, /* [out] */ ISimpleDOMNode **newNodePtr);")
+cpp_quote("// ---------------------------------------------------------------------------------------------------=")
+cpp_quote("// DOM navigation - get a different node.")
+cpp_quote("//")
+cpp_quote("// get_innerHTML(/* [out] */ BSTR *htmlText);")
+cpp_quote("// ---------------------------------------------------------------------------------------------------=")
+cpp_quote("// Returns HTML of this DOM node's subtree. Does not include the start and end tag for this node/element.")
+cpp_quote("//")
+cpp_quote("//")
+cpp_quote("// get_localInterface(/* [out] */ void **localInterface);")
+cpp_quote("// ---------------------------------------------------------------------------------------------------=")
+cpp_quote("// Only available in Gecko's process")
+cpp_quote("//")
+cpp_quote("//")
+cpp_quote("// get_language(/* [out] */ BSTR *htmlText);")
+cpp_quote("// ---------------------------------------------------------------------------------------------------=")
+cpp_quote("// Returns the computed language for this node, or empty string if unknown.")
+cpp_quote("//")
+cpp_quote("//")
+cpp_quote("///////////////////////////////////////////////////////////////////////////////////////////////////////")
+cpp_quote("")
+cpp_quote("")
+
+import "objidl.idl";
+import "oaidl.idl";
+
+import "ISimpleDOMText.idl";
+import "ISimpleDOMDocument.idl";
+
+[object, uuid(1814ceeb-49e2-407f-af99-fa755a7d2607)]
+interface ISimpleDOMNode : IUnknown
+{
+ const unsigned short NODETYPE_ELEMENT = 1;
+ const unsigned short NODETYPE_ATTRIBUTE = 2;
+ const unsigned short NODETYPE_TEXT = 3;
+ const unsigned short NODETYPE_CDATA_SECTION = 4;
+ const unsigned short NODETYPE_ENTITY_REFERENCE = 5;
+ const unsigned short NODETYPE_ENTITY = 6;
+ const unsigned short NODETYPE_PROCESSING_INSTRUCTION = 7;
+ const unsigned short NODETYPE_COMMENT = 8;
+ const unsigned short NODETYPE_DOCUMENT = 9;
+ const unsigned short NODETYPE_DOCUMENT_TYPE = 10;
+ const unsigned short NODETYPE_DOCUMENT_FRAGMENT = 11;
+ const unsigned short NODETYPE_NOTATION = 12;
+
+ [propget] HRESULT nodeInfo(
+ [out] BSTR *nodeName, // for performance returns NULL for text nodes (true nodeName would be "#text")
+ [out] short *nameSpaceID,
+ [out] BSTR *nodeValue,
+ [out] unsigned int *numChildren,
+ [out] unsigned int *uniqueID, // In Win32 accessible events we generate, the target's childID matches to this
+ [out, retval] unsigned short *nodeType
+ );
+
+ [propget] HRESULT attributes(
+ [in] unsigned short maxAttribs,
+ [out, size_is(maxAttribs), length_is(*numAttribs)] BSTR *attribNames,
+ [out, size_is(maxAttribs), length_is(*numAttribs)] short *nameSpaceID,
+ [out, size_is(maxAttribs), length_is(*numAttribs)] BSTR *attribValues,
+ [out, retval] unsigned short *numAttribs
+ );
+
+ [propget] HRESULT attributesForNames(
+ [in] unsigned short numAttribs,
+ [in, size_is(numAttribs), length_is(numAttribs)] BSTR *attribNames,
+ [in, size_is(numAttribs), length_is(numAttribs)] short *nameSpaceID,
+ [out, retval, size_is(numAttribs), length_is(numAttribs)] BSTR *attribValues
+ );
+
+ [propget] HRESULT computedStyle(
+ [in] unsigned short maxStyleProperties,
+ [in] boolean useAlternateView, // If TRUE, returns properites for media as set in nsIDOMDocument::set_alternateViewMediaTypes
+ [out, size_is(maxStyleProperties), length_is(*numStyleProperties)] BSTR *styleProperties,
+ [out, size_is(maxStyleProperties), length_is(*numStyleProperties)] BSTR *styleValues,
+ [out, retval] unsigned short *numStyleProperties
+ );
+
+ [propget] HRESULT computedStyleForProperties(
+ [in] unsigned short numStyleProperties,
+ [in] boolean useAlternateView, // If TRUE, returns properites for media as set in nsIDOMDocument::set_alternateViewMediaTypes
+ [in, size_is(numStyleProperties), length_is(numStyleProperties)] BSTR *styleProperties,
+ [out, retval, size_is(numStyleProperties), length_is(numStyleProperties)] BSTR *styleValues
+ );
+
+ HRESULT scrollTo([in] boolean placeTopLeft);
+
+ [propget] HRESULT parentNode([out, retval] ISimpleDOMNode **node);
+ [propget] HRESULT firstChild([out, retval] ISimpleDOMNode **node);
+ [propget] HRESULT lastChild([out, retval] ISimpleDOMNode **node);
+ [propget] HRESULT previousSibling([out, retval] ISimpleDOMNode **node);
+ [propget] HRESULT nextSibling([out, retval] ISimpleDOMNode **node);
+ [propget] HRESULT childAt([in] unsigned childIndex,
+ [out, retval] ISimpleDOMNode **node);
+
+ [propget] HRESULT innerHTML([out, retval] BSTR *innerHTML);
+
+ [propget, local] HRESULT localInterface([out][retval] void **localInterface);
+
+ [propget] HRESULT language([out, retval] BSTR *language);
+}
+
+
+[
+ uuid(a6245497-9c0b-4449-85a5-bd6ad07df8ea),
+ helpstring("ISimpleDOM Type Library")
+]
+library ISimpleDOM
+{
+ interface ISimpleDOMNode;
+ interface ISimpleDOMText;
+ interface ISimpleDOMDocument;
+};
+
diff --git a/accessible/interfaces/msaa/ISimpleDOMText.idl b/accessible/interfaces/msaa/ISimpleDOMText.idl
new file mode 100644
index 000000000..87bccfe61
--- /dev/null
+++ b/accessible/interfaces/msaa/ISimpleDOMText.idl
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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/. */
+
+import "objidl.idl";
+import "oaidl.idl";
+
+cpp_quote("///////////////////////////////////////////////////////////////////////////////////////////////////////")
+cpp_quote("//")
+cpp_quote("// ISimpleDOMText")
+cpp_quote("// ---------------------------------------------------------------------------------------------------=")
+cpp_quote("// An interface that extends MSAA's IAccessible to provide important additional capabilities on text nodes")
+cpp_quote("//")
+cpp_quote("// [propget] domText(/* out,retval */ BSTR *domText")
+cpp_quote("// ---------------------------------------------------------------------------------------------------=")
+cpp_quote("// Similar to IAccessible::get_accName, but does not strip out whitespace characters.")
+cpp_quote("// Important for retrieving the correct start/end substring indices to use with other")
+cpp_quote("// methods in ISimpleDOMText.")
+cpp_quote("//")
+cpp_quote("//")
+cpp_quote("// get_[un]clippedSubstringBounds(")
+cpp_quote("// /* [in] */ unsigned int startIndex,")
+cpp_quote("// /* [in] */ unsigned int endIndex,")
+cpp_quote("// /* [out] */ int *x,")
+cpp_quote("// /* [out] */ int *y,")
+cpp_quote("// /* [out] */ int *width,")
+cpp_quote("// /* [out] */ int *height);")
+cpp_quote("// ---------------------------------------------------------------------------------------------------=")
+cpp_quote("// Both methods get_clippedSubstringBounds and get_unclippedSubstringBounds return the screen pixel")
+cpp_quote("// coordinates of the given text substring. The in parameters for start and end indices refer")
+cpp_quote("// to the string returned by ISimpleDOMText::get_domText().")
+cpp_quote("//")
+cpp_quote("//")
+cpp_quote("// scrollToSubstring(")
+cpp_quote("// /* [in] */ unsigned int startIndex,")
+cpp_quote("// /* [in] */ unsigned int endIndex);")
+cpp_quote("// ---------------------------------------------------------------------------------------------------=")
+cpp_quote("// In scrollable views, scrolls to ensure that the specified substring is visible onscreen.")
+cpp_quote("// The in parameters for start and end indices refer to the string returned")
+cpp_quote("// by ISimpleDOMText::get_domText().")
+cpp_quote("//")
+cpp_quote("//")
+cpp_quote("// [propget] fontFamily(/* out,retval */ BSTR *fontFamily);")
+cpp_quote("// ---------------------------------------------------------------------------------------------------=")
+cpp_quote("// Return a single computed font family name, which is better than the comma delineated list")
+cpp_quote("// that is returned by the ISimpleDOMNode computed style methods for font-family.")
+cpp_quote("// In other words, return something like 'Arial' instead of 'Arial, Helvetica, Sans-serif'.")
+cpp_quote("///////////////////////////////////////////////////////////////////////////////////////////////////////")
+cpp_quote("")
+cpp_quote("")
+
+[object, uuid(4e747be5-2052-4265-8af0-8ecad7aad1c0)]
+interface ISimpleDOMText: IUnknown
+{
+ // Includes whitespace in DOM
+ [propget] HRESULT domText([out, retval] BSTR *domText);
+
+ HRESULT get_clippedSubstringBounds([in] unsigned int startIndex,
+ [in] unsigned int endIndex,
+ [out] int *x,
+ [out] int *y,
+ [out] int *width,
+ [out] int *height);
+
+ HRESULT get_unclippedSubstringBounds([in] unsigned int startIndex,
+ [in] unsigned int endIndex,
+ [out] int *x,
+ [out] int *y,
+ [out] int *width,
+ [out] int *height);
+
+ HRESULT scrollToSubstring([in] unsigned int startIndex,
+ [in] unsigned int endIndex);
+
+ [propget] HRESULT fontFamily([out, retval] BSTR *fontFamily);
+};
+
diff --git a/accessible/interfaces/msaa/Makefile.in b/accessible/interfaces/msaa/Makefile.in
new file mode 100644
index 000000000..28a11adb4
--- /dev/null
+++ b/accessible/interfaces/msaa/Makefile.in
@@ -0,0 +1,50 @@
+# 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/.
+
+GARBAGE += $(MIDL_GENERATED_FILES) done_gen dlldata.c
+
+MIDL_GENERATED_FILES = \
+ ISimpleDOMNode.h \
+ ISimpleDOMNode_p.c \
+ ISimpleDOMNode_i.c \
+ ISimpleDOMDocument.h \
+ ISimpleDOMDocument_p.c \
+ ISimpleDOMDocument_i.c \
+ ISimpleDOMText.h \
+ ISimpleDOMText_p.c \
+ ISimpleDOMText_i.c \
+ $(NULL)
+
+$(MIDL_GENERATED_FILES): done_gen
+
+done_gen: ISimpleDOMNode.idl \
+ ISimpleDOMDocument.idl \
+ ISimpleDOMText.idl
+
+ $(MIDL) $(MIDL_FLAGS) -I $(srcdir) -Oicf $(srcdir)/ISimpleDOMNode.idl
+ $(MIDL) $(MIDL_FLAGS) -Oicf $(srcdir)/ISimpleDOMDocument.idl
+ $(MIDL) $(MIDL_FLAGS) -Oicf $(srcdir)/ISimpleDOMText.idl
+ touch $@
+
+export:: done_gen
+
+# This marshall dll is also registered in the installer
+register::
+ regsvr32 -s $(DIST)/bin/$(SHARED_LIBRARY)
+
+EMBED_MANIFEST_AT = 2
+
+midl_exports := \
+ ISimpleDOMDocument.h \
+ ISimpleDOMDocument_i.c \
+ ISimpleDOMNode.h \
+ ISimpleDOMNode_i.c \
+ ISimpleDOMText.h \
+ ISimpleDOMText_i.c \
+ $(NULL)
+
+INSTALL_TARGETS += midl_exports
+midl_exports_FILES := $(midl_exports)
+midl_exports_DEST = $(DIST)/include
+midl_exports_TARGET := export
diff --git a/accessible/interfaces/msaa/moz.build b/accessible/interfaces/msaa/moz.build
new file mode 100644
index 000000000..7f50b3047
--- /dev/null
+++ b/accessible/interfaces/msaa/moz.build
@@ -0,0 +1,41 @@
+# -*- 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/.
+
+GeckoSharedLibrary('AccessibleMarshal', linkage=None)
+
+SOURCES += [
+ '!dlldata.c',
+ '!ISimpleDOMDocument_i.c',
+ '!ISimpleDOMDocument_p.c',
+ '!ISimpleDOMNode_i.c',
+ '!ISimpleDOMNode_p.c',
+ '!ISimpleDOMText_i.c',
+ '!ISimpleDOMText_p.c',
+]
+
+DEFINES['REGISTER_PROXY_DLL'] = True
+
+DEFFILE = SRCDIR + '/AccessibleMarshal.def'
+
+OS_LIBS += [
+ 'kernel32',
+ 'rpcrt4',
+ 'oleaut32',
+]
+
+GENERATED_FILES += [
+ 'ISimpleDOMNode.tlb',
+]
+
+RCINCLUDE = 'AccessibleMarshal.rc'
+
+# The Windows MIDL code generator creates things like:
+#
+# #endif !_MIDL_USE_GUIDDEF_
+#
+# which clang-cl complains about. MSVC doesn't, so turn this warning off.
+if CONFIG['CLANG_CL']:
+ CFLAGS += ['-Wno-extra-tokens']
diff --git a/accessible/interfaces/nsIAccessibilityService.idl b/accessible/interfaces/nsIAccessibilityService.idl
new file mode 100644
index 000000000..c073eaebf
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibilityService.idl
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* 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 "nsISupports.idl"
+
+interface nsIDOMNode;
+interface nsIAccessible;
+interface nsIWeakReference;
+interface nsIPresShell;
+interface nsIAccessiblePivot;
+
+/**
+ * An interface for in-process accessibility clients wishing to get an
+ * nsIAccessible for a given DOM node. More documentation at:
+ * http://www.mozilla.org/projects/ui/accessibility
+ */
+[scriptable, builtinclass, uuid(9a6f80fe-25cc-405c-9f8f-25869bc9f94e)]
+interface nsIAccessibilityService : nsISupports
+{
+ /**
+ * Return application accessible.
+ */
+ nsIAccessible getApplicationAccessible();
+
+ /**
+ * Return an nsIAccessible for a DOM node in pres shell 0.
+ * Create a new accessible of the appropriate type if necessary,
+ * or use one from the accessibility cache if it already exists.
+ * @param aNode The DOM node to get an accessible for.
+ * @return The nsIAccessible for the given DOM node.
+ */
+ nsIAccessible getAccessibleFor(in nsIDOMNode aNode);
+
+ /**
+ * Returns accessible role as a string.
+ *
+ * @param aRole - the accessible role constants.
+ */
+ AString getStringRole(in unsigned long aRole);
+
+ /**
+ * Returns list which contains accessible states as a strings.
+ *
+ * @param aStates - accessible states.
+ * @param aExtraStates - accessible extra states.
+ */
+ nsISupports getStringStates(in unsigned long aStates,
+ in unsigned long aExtraStates);
+
+ /**
+ * Get the type of accessible event as a string.
+ *
+ * @param aEventType - the accessible event type constant
+ * @return - accessible event type presented as human readable string
+ */
+ AString getStringEventType(in unsigned long aEventType);
+
+ /**
+ * Get the type of accessible relation as a string.
+ *
+ * @param aRelationType - the accessible relation type constant
+ * @return - accessible relation type presented as human readable string
+ */
+ AString getStringRelationType(in unsigned long aRelationType);
+
+ /**
+ * Return an accessible for the given DOM node from the cache.
+ * @note the method is intended for testing purposes
+ *
+ * @param aNode [in] the DOM node to get an accessible for
+ *
+ * @return cached accessible for the given DOM node if any
+ */
+ nsIAccessible getAccessibleFromCache(in nsIDOMNode aNode);
+
+ /**
+ * Create a new pivot for tracking a position and traversing a subtree.
+ *
+ * @param aRoot [in] the accessible root for the pivot
+ * @return a new pivot
+ */
+ nsIAccessiblePivot createAccessiblePivot(in nsIAccessible aRoot);
+
+ /**
+ * Enable logging for the given modules, all other modules aren't logged.
+ *
+ * @param aModules [in] list of modules, format is comma separated list
+ * like 'docload,doccreate'.
+ * @note Works on debug build only.
+ * @see Logging.cpp for list of possible values.
+ */
+ void setLogging(in ACString aModules);
+
+ /**
+ * Return true if the given module is logged.
+ */
+ boolean isLogged(in AString aModule);
+};
+
+/**
+ * @deprecated, use nsIAccessibilityService instead.
+ */
+[scriptable, builtinclass, uuid(d85e0cbe-47ce-490c-8488-f821dd2be0c2)]
+interface nsIAccessibleRetrieval : nsIAccessibilityService
+{
+};
diff --git a/accessible/interfaces/nsIAccessible.idl b/accessible/interfaces/nsIAccessible.idl
new file mode 100644
index 000000000..9048e7644
--- /dev/null
+++ b/accessible/interfaces/nsIAccessible.idl
@@ -0,0 +1,300 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* 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 "nsISupports.idl"
+#include "nsIArray.idl"
+
+interface nsIPersistentProperties;
+interface nsIDOMCSSPrimitiveValue;
+interface nsIDOMNode;
+interface nsIAccessibleDocument;
+interface nsIAccessibleRelation;
+
+%{C++
+namespace mozilla {
+namespace a11y {
+class Accessible;
+}
+}
+%}
+
+/**
+ * A cross-platform interface that supports platform-specific
+ * accessibility APIs like MSAA and ATK. Contains the sum of what's needed
+ * to support IAccessible as well as ATK's generic accessibility objects.
+ * Can also be used by in-process accessibility clients to get information
+ * about objects in the accessible tree. The accessible tree is a subset of
+ * nodes in the DOM tree -- such as documents, focusable elements and text.
+ * Mozilla creates the implementations of nsIAccessible on demand.
+ * See http://www.mozilla.org/projects/ui/accessibility for more information.
+ */
+[scriptable, builtinclass, uuid(de2869d9-563c-4943-996b-31a4daa4d097)]
+interface nsIAccessible : nsISupports
+{
+ /**
+ * Parent node in accessible tree.
+ */
+ readonly attribute nsIAccessible parent;
+
+ /**
+ * Next sibling in accessible tree
+ */
+ readonly attribute nsIAccessible nextSibling;
+
+ /**
+ * Previous sibling in accessible tree
+ */
+ readonly attribute nsIAccessible previousSibling;
+
+ /**
+ * First child in accessible tree
+ */
+ readonly attribute nsIAccessible firstChild;
+
+ /**
+ * Last child in accessible tree
+ */
+ readonly attribute nsIAccessible lastChild;
+
+ /**
+ * Array of all this element's children.
+ */
+ readonly attribute nsIArray children;
+
+ /**
+ * Number of accessible children
+ */
+ readonly attribute long childCount;
+
+ /**
+ * The 0-based index of this accessible in its parent's list of children,
+ * or -1 if this accessible does not have a parent.
+ */
+ readonly attribute long indexInParent;
+
+ /**
+ * The DOM node this nsIAccessible is associated with.
+ */
+ readonly attribute nsIDOMNode DOMNode;
+
+ /**
+ * For remote accessibles the id of the related DOM node.
+ */
+ readonly attribute DOMString id;
+
+ /**
+ * The document accessible that this access node resides in.
+ */
+ readonly attribute nsIAccessibleDocument document;
+
+ /**
+ * The root document accessible that this access node resides in.
+ */
+ readonly attribute nsIAccessibleDocument rootDocument;
+
+ /**
+ * The language for the current DOM node, e.g. en, de, etc.
+ */
+ readonly attribute DOMString language;
+
+ /**
+ * Accessible name -- the main text equivalent for this node. The name is
+ * specified by ARIA or by native markup. Example of ARIA markup is
+ * aria-labelledby attribute placed on element of this accessible. Example
+ * of native markup is HTML label linked with HTML element of this accessible.
+ *
+ * Value can be string or null. A null value indicates that AT may attempt to
+ * compute the name. Any string value, including the empty string, should be
+ * considered author-intentional, and respected.
+ */
+ readonly attribute AString name;
+
+ /**
+ * Accessible value -- a number or a secondary text equivalent for this node
+ * Widgets that use role attribute can force a value using the valuenow attribute
+ */
+ readonly attribute AString value;
+
+ /**
+ * Accessible description -- long text associated with this node
+ */
+ readonly attribute AString description;
+
+ /**
+ * Provides localized string of accesskey name, such as Alt+D.
+ * The modifier may be affected by user and platform preferences.
+ * Usually alt+letter, or just the letter alone for menu items.
+ */
+ readonly attribute AString accessKey;
+
+ /**
+ * Provides localized string of global keyboard accelerator for default
+ * action, such as Ctrl+O for Open file
+ */
+ readonly attribute AString keyboardShortcut;
+
+ /**
+ * Enumerated accessible role (see the constants defined in nsIAccessibleRole).
+ *
+ * @note The values might depend on platform because of variations. Widgets
+ * can use ARIA role attribute to force the final role.
+ */
+ readonly attribute unsigned long role;
+
+ /**
+ * Accessible states -- bit fields which describe boolean properties of node.
+ * Many states are only valid given a certain role attribute that supports
+ * them.
+ *
+ * @param aState - the first bit field (see nsIAccessibleStates::STATE_*
+ * constants)
+ * @param aExtraState - the second bit field
+ * (see nsIAccessibleStates::EXT_STATE_* constants)
+ */
+ void getState(out unsigned long aState, out unsigned long aExtraState);
+
+ /**
+ * Help text associated with node
+ *
+ * @note As of now, this just returns empty string.
+ */
+ readonly attribute AString help;
+
+ /**
+ * Focused accessible child of node
+ */
+ readonly attribute nsIAccessible focusedChild;
+
+ /**
+ * Attributes of accessible
+ */
+ readonly attribute nsIPersistentProperties attributes;
+
+ /**
+ * Returns grouping information. Used for tree items, list items, tab panel
+ * labels, radio buttons, etc. Also used for collectons of non-text objects.
+ *
+ * @param groupLevel - 1-based, similar to ARIA 'level' property
+ * @param similarItemsInGroup - 1-based, similar to ARIA 'setsize' property,
+ * inclusive of the current item
+ * @param positionInGroup - 1-based, similar to ARIA 'posinset' property
+ */
+ void groupPosition(out long aGroupLevel, out long aSimilarItemsInGroup,
+ out long aPositionInGroup);
+
+ /**
+ * Accessible child which contains the coordinate at (x, y) in screen pixels.
+ * If the point is in the current accessible but not in a child, the
+ * current accessible will be returned.
+ * If the point is in neither the current accessible or a child, then
+ * null will be returned.
+ *
+ * @param x screen's x coordinate
+ * @param y screen's y coordinate
+ * @return the deepest accessible child containing the given point
+ */
+ nsIAccessible getChildAtPoint(in long x, in long y);
+
+ /**
+ * Deepest accessible child which contains the coordinate at (x, y) in screen
+ * pixels. If the point is in the current accessible but not in a child, the
+ * current accessible will be returned. If the point is in neither the current
+ * accessible or a child, then null will be returned.
+ *
+ * @param x screen's x coordinate
+ * @param y screen's y coordinate
+ * @return the deepest accessible child containing the given point
+ */
+ nsIAccessible getDeepestChildAtPoint(in long x, in long y);
+
+ /**
+ * Nth accessible child using zero-based index or last child if index less than zero
+ */
+ nsIAccessible getChildAt(in long aChildIndex);
+
+ /**
+ * Return accessible relation by the given relation type (see.
+ * constants defined in nsIAccessibleRelation).
+ */
+ nsIAccessibleRelation getRelationByType(in unsigned long aRelationType);
+
+ /**
+ * Returns multiple accessible relations for this object.
+ */
+ nsIArray getRelations();
+
+ /**
+ * Return accessible's x and y coordinates relative to the screen and
+ * accessible's width and height.
+ */
+ void getBounds(out long x, out long y, out long width, out long height);
+
+ /**
+ * Add or remove this accessible to the current selection
+ */
+ void setSelected(in boolean isSelected);
+
+ /**
+ * Select this accessible node only
+ */
+ void takeSelection();
+
+ /**
+ * Focus this accessible node,
+ * The state STATE_FOCUSABLE indicates whether this node is normally focusable.
+ * It is the callers responsibility to determine whether this node is focusable.
+ * accTakeFocus on a node that is not normally focusable (such as a table),
+ * will still set focus on that node, although normally that will not be visually
+ * indicated in most style sheets.
+ */
+ void takeFocus();
+
+ /**
+ * The number of accessible actions associated with this accessible
+ */
+ readonly attribute uint8_t actionCount;
+
+ /**
+ * The name of the accessible action at the given zero-based index
+ */
+ AString getActionName(in uint8_t index);
+
+ /**
+ * The description of the accessible action at the given zero-based index
+ */
+ AString getActionDescription(in uint8_t aIndex);
+
+ /**
+ * Perform the accessible action at the given zero-based index
+ * Action number 0 is the default action
+ */
+ void doAction(in uint8_t index);
+
+ /**
+ * Makes an object visible on screen.
+ *
+ * @param scrollType - defines where the object should be placed on
+ * the screen (see nsIAccessibleScrollType for
+ * available constants).
+ */
+ void scrollTo(in unsigned long aScrollType);
+
+ /**
+ * Moves the top left of an object to a specified location.
+ *
+ * @param coordinateType [in] - specifies whether the coordinates are relative to
+ * the screen or the parent object (for available
+ * constants refer to nsIAccessibleCoordinateType)
+ * @param x [in] - defines the x coordinate
+ * @param y [in] - defines the y coordinate
+ */
+ void scrollToPoint(in unsigned long coordinateType, in long x, in long y);
+
+ %{C++
+ virtual mozilla::a11y::Accessible* ToInternalAccessible() const = 0;
+ %}
+
+};
+
diff --git a/accessible/interfaces/nsIAccessibleApplication.idl b/accessible/interfaces/nsIAccessibleApplication.idl
new file mode 100644
index 000000000..f3e472fed
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibleApplication.idl
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* 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 "nsISupports.idl"
+
+/**
+ * This interface is implemented by top level accessible object in hierarchy and
+ * provides information about application.
+ */
+[scriptable, builtinclass, uuid(79251626-387c-4531-89f3-680d31d6cf05)]
+interface nsIAccessibleApplication : nsISupports
+{
+ /**
+ * Returns the application name.
+ */
+ readonly attribute DOMString appName;
+
+ /**
+ * Returns the application version.
+ */
+ readonly attribute DOMString appVersion;
+
+ /**
+ * Returns the platform name.
+ */
+ readonly attribute DOMString platformName;
+
+ /**
+ * Returns the platform version.
+ */
+ readonly attribute DOMString platformVersion;
+};
diff --git a/accessible/interfaces/nsIAccessibleCaretMoveEvent.idl b/accessible/interfaces/nsIAccessibleCaretMoveEvent.idl
new file mode 100644
index 000000000..e38bc11a7
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibleCaretMoveEvent.idl
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* 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 "nsIAccessibleEvent.idl"
+
+/**
+ * Fired when the caret changes position in text.
+ */
+[scriptable, builtinclass, uuid(ed1982e4-57d7-41a8-8cd8-9023f809383e)]
+interface nsIAccessibleCaretMoveEvent: nsIAccessibleEvent
+{
+ /**
+ * Return caret offset.
+ */
+ readonly attribute long caretOffset;
+};
diff --git a/accessible/interfaces/nsIAccessibleDocument.idl b/accessible/interfaces/nsIAccessibleDocument.idl
new file mode 100644
index 000000000..071847dd2
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibleDocument.idl
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* 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 "nsISupports.idl"
+
+interface nsIAccessiblePivot;
+interface nsIDOMDocument;
+interface mozIDOMWindowProxy;
+
+/**
+ * An interface for in-process accessibility clients
+ * that wish to retrieve information about a document.
+ * When accessibility is turned on in Gecko,
+ * there is an nsIAccessibleDocument for each document
+ * whether it is XUL, HTML or whatever.
+ * You can QueryInterface to nsIAccessibleDocument from the nsIAccessible for
+ * the root node of a document or you can get one from
+ * nsIAccessible::GetDocument().
+ */
+[scriptable, builtinclass, uuid(5cad5f91-fcce-40e7-913e-4671701d19b4)]
+interface nsIAccessibleDocument : nsISupports
+{
+ /**
+ * The URL of the document
+ */
+ readonly attribute AString URL;
+
+ /**
+ * The title of the document, as specified in the document.
+ */
+ readonly attribute AString title;
+
+ /**
+ * The mime type of the document
+ */
+ readonly attribute AString mimeType;
+
+ /**
+ * The doc type of the document, as specified in the document.
+ */
+ readonly attribute AString docType;
+
+ /**
+ * The nsIDOMDocument interface associated with this document.
+ */
+ readonly attribute nsIDOMDocument DOMDocument;
+
+ /**
+ * The nsIDOMWindow that the document resides in.
+ */
+ readonly attribute mozIDOMWindowProxy window;
+
+ /**
+ * Return the parent document accessible.
+ */
+ readonly attribute nsIAccessibleDocument parentDocument;
+
+ /**
+ * Return the count of child document accessibles.
+ */
+ readonly attribute unsigned long childDocumentCount;
+
+ /**
+ * The virtual cursor pivot this document manages.
+ */
+ readonly attribute nsIAccessiblePivot virtualCursor;
+
+ /**
+ * Return the child document accessible at the given index.
+ */
+ nsIAccessibleDocument getChildDocumentAt(in unsigned long index);
+};
diff --git a/accessible/interfaces/nsIAccessibleEditableText.idl b/accessible/interfaces/nsIAccessibleEditableText.idl
new file mode 100644
index 000000000..ac5d97437
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibleEditableText.idl
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 "nsISupports.idl"
+
+[scriptable, builtinclass, uuid(28915cca-3366-4034-ba1d-b7afb9b37639)]
+interface nsIAccessibleEditableText : nsISupports
+{
+ /**
+ * Replaces the text represented by this object by the given text.
+ */
+ void setTextContents (in AString text);
+
+ /**
+ * Inserts text at the specified position.
+ *
+ * @param text - text that is inserted.
+ * @param position - index at which to insert the text.
+ */
+ void insertText(in AString text, in long position);
+
+ /**
+ * Copies the text range into the clipboard.
+ *
+ * @param startPos - start index of the text to moved into the clipboard.
+ * @param endPos - end index of the text to moved into the clipboard.
+ */
+ void copyText(in long startPos, in long endPos);
+
+ /**
+ * Deletes a range of text and copies it to the clipboard.
+ *
+ * @param startPos - start index of the text to be deleted.
+ * @param endOffset - end index of the text to be deleted.
+ */
+ void cutText(in long startPos, in long endPos);
+
+ /**
+ * Deletes a range of text.
+ *
+ * @param startPos - start index of the text to be deleted.
+ * @param endPos - end index of the text to be deleted.
+ */
+ void deleteText(in long startPos, in long endPos);
+
+ /**
+ * Pastes text from the clipboard.
+ *
+ * @param position - index at which to insert the text from the system
+ * clipboard into the text represented by this object.
+ */
+ void pasteText(in long position);
+};
diff --git a/accessible/interfaces/nsIAccessibleEvent.idl b/accessible/interfaces/nsIAccessibleEvent.idl
new file mode 100644
index 000000000..c4ab81e40
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibleEvent.idl
@@ -0,0 +1,455 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* 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 "nsISupports.idl"
+
+interface nsIAccessible;
+interface nsIAccessibleDocument;
+interface nsIDOMNode;
+
+%{C++
+#define NS_ACCESSIBLE_EVENT_TOPIC "accessible-event"
+%}
+
+/**
+ * An interface for accessibility events listened to
+ * by in-process accessibility clients, which can be used
+ * to find out how to get accessibility and DOM interfaces for
+ * the event and its target. To listen to in-process accessibility invents,
+ * make your object an nsIObserver, and listen for accessible-event by
+ * using code something like this:
+ * nsCOMPtr<nsIObserverService> observerService =
+ * do_GetService("@mozilla.org/observer-service;1", &rv);
+ * if (NS_SUCCEEDED(rv))
+ * rv = observerService->AddObserver(this, "accessible-event", PR_TRUE);
+ */
+[scriptable, builtinclass, uuid(20c69a40-6c2c-42a3-a578-6f4473aab9dd)]
+interface nsIAccessibleEvent : nsISupports
+{
+ /**
+ * An object has been created.
+ */
+ const unsigned long EVENT_SHOW = 0x0001;
+
+ /**
+ * An object has been destroyed.
+ */
+ const unsigned long EVENT_HIDE = 0x0002;
+
+ /**
+ * An object's children have changed
+ */
+ const unsigned long EVENT_REORDER = 0x0003;
+
+ /**
+ * The active descendant of a component has changed. The active descendant
+ * is used in objects with transient children.
+ */
+ const unsigned long EVENT_ACTIVE_DECENDENT_CHANGED = 0x0004;
+
+ /**
+ * An object has received the keyboard focus.
+ */
+ const unsigned long EVENT_FOCUS = 0x0005;
+
+ /**
+ * An object's state has changed.
+ */
+ const unsigned long EVENT_STATE_CHANGE = 0x0006;
+
+ /**
+ * An object has changed location, shape, or size.
+ */
+ const unsigned long EVENT_LOCATION_CHANGE = 0x0007;
+
+ /**
+ * An object's Name property has changed.
+ */
+ const unsigned long EVENT_NAME_CHANGE = 0x0008;
+
+ /**
+ * An object's Description property has changed.
+ */
+ const unsigned long EVENT_DESCRIPTION_CHANGE = 0x0009;
+
+ /**
+ * An object's numeric Value has changed.
+ */
+ const unsigned long EVENT_VALUE_CHANGE = 0x000A;
+
+ /**
+ * An object's help has changed.
+ */
+ const unsigned long EVENT_HELP_CHANGE = 0x000B;
+
+ /**
+ * An object's default action has changed.
+ */
+ const unsigned long EVENT_DEFACTION_CHANGE = 0x000C;
+
+ /**
+ * An object's action has changed.
+ */
+ const unsigned long EVENT_ACTION_CHANGE = 0x000D;
+
+ /**
+ * An object's keyboard shortcut has changed.
+ */
+ const unsigned long EVENT_ACCELERATOR_CHANGE = 0x000E;
+
+ /**
+ * The selection within a container object has changed.
+ */
+ const unsigned long EVENT_SELECTION = 0x000F;
+
+ /**
+ * An item within a container object has been added to the selection.
+ */
+ const unsigned long EVENT_SELECTION_ADD = 0x0010;
+
+ /**
+ * An item within a container object has been removed from the selection.
+ */
+ const unsigned long EVENT_SELECTION_REMOVE = 0x0011;
+
+ /**
+ * Numerous selection changes have occurred within a container object.
+ */
+ const unsigned long EVENT_SELECTION_WITHIN = 0x0012;
+
+ /**
+ * An alert has been generated. Server applications send this event when a
+ * user needs to know that a user interface element has changed.
+ */
+ const unsigned long EVENT_ALERT = 0x0013;
+
+ /**
+ * The foreground window has changed.
+ */
+ const unsigned long EVENT_FOREGROUND = 0x0014;
+
+ /**
+ * A menu item on the menu bar has been selected.
+ */
+ const unsigned long EVENT_MENU_START = 0x0015;
+
+ /**
+ * A menu from the menu bar has been closed.
+ */
+ const unsigned long EVENT_MENU_END = 0x0016;
+
+ /**
+ * A pop-up menu has been displayed.
+ */
+ const unsigned long EVENT_MENUPOPUP_START = 0x0017;
+
+ /**
+ * A pop-up menu has been closed.
+ */
+ const unsigned long EVENT_MENUPOPUP_END = 0x0018;
+
+ /**
+ * A window has received mouse capture.
+ */
+ const unsigned long EVENT_CAPTURE_START = 0x0019;
+
+ /**
+ * A window has lost mouse capture.
+ */
+ const unsigned long EVENT_CAPTURE_END = 0x001A;
+
+ /**
+ * A window is being moved or resized.
+ */
+ const unsigned long EVENT_MOVESIZE_START = 0x001B;
+
+ /**
+ * The movement or resizing of a window has finished
+ */
+ const unsigned long EVENT_MOVESIZE_END = 0x001C;
+
+ /**
+ * A window has entered context-sensitive Help mode
+ */
+ const unsigned long EVENT_CONTEXTHELP_START = 0x001D;
+
+ /**
+ * A window has exited context-sensitive Help mode
+ */
+ const unsigned long EVENT_CONTEXTHELP_END = 0x001E;
+
+ /**
+ * An application is about to enter drag-and-drop mode
+ */
+ const unsigned long EVENT_DRAGDROP_START = 0x001F;
+
+ /**
+ * An application is about to exit drag-and-drop mode
+ */
+ const unsigned long EVENT_DRAGDROP_END = 0x0020;
+
+ /**
+ * A dialog box has been displayed
+ */
+ const unsigned long EVENT_DIALOG_START = 0x0021;
+
+ /**
+ * A dialog box has been closed
+ */
+ const unsigned long EVENT_DIALOG_END = 0x0022;
+
+ /**
+ * Scrolling has started on a scroll bar
+ */
+ const unsigned long EVENT_SCROLLING_START = 0x0023;
+
+ /**
+ * Scrolling has ended on a scroll bar
+ */
+ const unsigned long EVENT_SCROLLING_END = 0x0024;
+
+ /**
+ * A window object is about to be minimized or maximized
+ */
+ const unsigned long EVENT_MINIMIZE_START = 0x0025;
+
+ /**
+ * A window object has been minimized or maximized
+ */
+ const unsigned long EVENT_MINIMIZE_END = 0x0026;
+
+ /**
+ * The loading of the document has completed.
+ */
+ const unsigned long EVENT_DOCUMENT_LOAD_COMPLETE = 0x0027;
+
+ /**
+ * The document contents are being reloaded.
+ */
+ const unsigned long EVENT_DOCUMENT_RELOAD = 0x0028;
+
+ /**
+ * The loading of the document was interrupted.
+ */
+ const unsigned long EVENT_DOCUMENT_LOAD_STOPPED = 0x0029;
+
+ /**
+ * The document wide attributes of the document object have changed.
+ */
+ const unsigned long EVENT_DOCUMENT_ATTRIBUTES_CHANGED = 0x002A;
+
+ /**
+ * The contents of the document have changed.
+ */
+ const unsigned long EVENT_DOCUMENT_CONTENT_CHANGED = 0x002B;
+
+ const unsigned long EVENT_PROPERTY_CHANGED = 0x002C;
+
+ /**
+ * A slide changed in a presentation document or a page boundary was
+ * crossed in a word processing document.
+ */
+ const unsigned long EVENT_PAGE_CHANGED = 0x002D;
+
+ /**
+ * A text object's attributes changed.
+ * Also see EVENT_OBJECT_ATTRIBUTE_CHANGED.
+ */
+ const unsigned long EVENT_TEXT_ATTRIBUTE_CHANGED = 0x002E;
+
+ /**
+ * The caret has moved to a new position.
+ */
+ const unsigned long EVENT_TEXT_CARET_MOVED = 0x002F;
+
+ /**
+ * This event indicates general text changes, i.e. changes to text that is
+ * exposed through the IAccessibleText and IAccessibleEditableText interfaces.
+ */
+ const unsigned long EVENT_TEXT_CHANGED = 0x0030;
+
+ /**
+ * Text was inserted.
+ */
+ const unsigned long EVENT_TEXT_INSERTED = 0x0031;
+
+ /**
+ * Text was removed.
+ */
+ const unsigned long EVENT_TEXT_REMOVED = 0x0032;
+
+ /**
+ * Text was updated.
+ */
+ const unsigned long EVENT_TEXT_UPDATED = 0x0033;
+
+ /**
+ * The text selection changed.
+ */
+ const unsigned long EVENT_TEXT_SELECTION_CHANGED = 0x0034;
+
+ /**
+ * A visibile data event indicates the change of the visual appearance
+ * of an accessible object. This includes for example most of the
+ * attributes available via the IAccessibleComponent interface.
+ */
+ const unsigned long EVENT_VISIBLE_DATA_CHANGED = 0x0035;
+
+ /**
+ * The caret moved from one column to the next.
+ */
+ const unsigned long EVENT_TEXT_COLUMN_CHANGED = 0x0036;
+
+ /**
+ * The caret moved from one section to the next.
+ */
+ const unsigned long EVENT_SECTION_CHANGED = 0x0037;
+
+ /**
+ * A table caption changed.
+ */
+ const unsigned long EVENT_TABLE_CAPTION_CHANGED = 0x0038;
+
+ /**
+ * A table's data changed.
+ */
+ const unsigned long EVENT_TABLE_MODEL_CHANGED = 0x0039;
+
+ /**
+ * A table's summary changed.
+ */
+ const unsigned long EVENT_TABLE_SUMMARY_CHANGED = 0x003A;
+
+ /**
+ * A table's row description changed.
+ */
+ const unsigned long EVENT_TABLE_ROW_DESCRIPTION_CHANGED = 0x003B;
+
+ /**
+ * A table's row header changed.
+ */
+ const unsigned long EVENT_TABLE_ROW_HEADER_CHANGED = 0x003C;
+
+ const unsigned long EVENT_TABLE_ROW_INSERT = 0x003D;
+ const unsigned long EVENT_TABLE_ROW_DELETE = 0x003E;
+ const unsigned long EVENT_TABLE_ROW_REORDER = 0x003F;
+
+ /**
+ * A table's column description changed.
+ */
+ const unsigned long EVENT_TABLE_COLUMN_DESCRIPTION_CHANGED = 0x0040;
+
+ /**
+ * A table's column header changed.
+ */
+ const unsigned long EVENT_TABLE_COLUMN_HEADER_CHANGED = 0x0041;
+
+ const unsigned long EVENT_TABLE_COLUMN_INSERT = 0x0042;
+ const unsigned long EVENT_TABLE_COLUMN_DELETE = 0x0043;
+ const unsigned long EVENT_TABLE_COLUMN_REORDER = 0x0044;
+
+ const unsigned long EVENT_WINDOW_ACTIVATE = 0x0045;
+ const unsigned long EVENT_WINDOW_CREATE = 0x0046;
+ const unsigned long EVENT_WINDOW_DEACTIVATE = 0x0047;
+ const unsigned long EVENT_WINDOW_DESTROY = 0x0048;
+ const unsigned long EVENT_WINDOW_MAXIMIZE = 0x0049;
+ const unsigned long EVENT_WINDOW_MINIMIZE = 0x004A;
+ const unsigned long EVENT_WINDOW_RESIZE = 0x004B;
+ const unsigned long EVENT_WINDOW_RESTORE = 0x004C;
+
+ /**
+ * The ending index of this link within the containing string has changed.
+ */
+ const unsigned long EVENT_HYPERLINK_END_INDEX_CHANGED = 0x004D;
+
+ /**
+ * The number of anchors assoicated with this hyperlink object has changed.
+ */
+ const unsigned long EVENT_HYPERLINK_NUMBER_OF_ANCHORS_CHANGED = 0x004E;
+
+ /**
+ * The hyperlink selected state changed from selected to unselected or
+ * from unselected to selected.
+ */
+ const unsigned long EVENT_HYPERLINK_SELECTED_LINK_CHANGED = 0x004F;
+
+ /**
+ * One of the links associated with the hypertext object has been activated.
+ */
+ const unsigned long EVENT_HYPERTEXT_LINK_ACTIVATED = 0x0050;
+
+ /**
+ * One of the links associated with the hypertext object has been selected.
+ */
+ const unsigned long EVENT_HYPERTEXT_LINK_SELECTED = 0x0051;
+
+ /**
+ * The starting index of this link within the containing string has changed.
+ */
+ const unsigned long EVENT_HYPERLINK_START_INDEX_CHANGED = 0x0052;
+
+ /**
+ * Focus has changed from one hypertext object to another, or focus moved
+ * from a non-hypertext object to a hypertext object, or focus moved from a
+ * hypertext object to a non-hypertext object.
+ */
+ const unsigned long EVENT_HYPERTEXT_CHANGED = 0x0053;
+
+ /**
+ * The number of hyperlinks associated with a hypertext object changed.
+ */
+ const unsigned long EVENT_HYPERTEXT_NLINKS_CHANGED = 0x0054;
+
+ /**
+ * An object's attributes changed. Also see EVENT_TEXT_ATTRIBUTE_CHANGED.
+ */
+ const unsigned long EVENT_OBJECT_ATTRIBUTE_CHANGED = 0x0055;
+
+ /**
+ * A cursorable's virtual cursor has changed.
+ */
+ const unsigned long EVENT_VIRTUALCURSOR_CHANGED = 0x0056;
+
+ /**
+ * An object's text Value has changed.
+ */
+ const unsigned long EVENT_TEXT_VALUE_CHANGE = 0x0057;
+
+ /**
+ * Help make sure event map does not get out-of-line.
+ */
+ const unsigned long EVENT_LAST_ENTRY = 0x0058;
+
+ /**
+ * The type of event, based on the enumerated event values
+ * defined in this interface.
+ */
+ readonly attribute unsigned long eventType;
+
+ /**
+ * The nsIAccessible associated with the event.
+ * May return null if no accessible is available
+ */
+ readonly attribute nsIAccessible accessible;
+
+ /**
+ * The nsIAccessibleDocument that the event target nsIAccessible
+ * resides in. This can be used to get the DOM window,
+ * the DOM document and the window handler, among other things.
+ */
+ readonly attribute nsIAccessibleDocument accessibleDocument;
+
+ /**
+ * The nsIDOMNode associated with the event
+ * May return null if accessible for event has been shut down
+ */
+ readonly attribute nsIDOMNode DOMNode;
+
+ /**
+ * Returns true if the event was caused by explicit user input,
+ * as opposed to purely originating from a timer or mouse movement
+ */
+ readonly attribute boolean isFromUserInput;
+};
diff --git a/accessible/interfaces/nsIAccessibleHideEvent.idl b/accessible/interfaces/nsIAccessibleHideEvent.idl
new file mode 100644
index 000000000..820b8914c
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibleHideEvent.idl
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* 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 "nsIAccessibleEvent.idl"
+
+/**
+ * Fired when a accessible and its subtree are removed from the tree.
+ */
+[scriptable, builtinclass, uuid(2051709a-4e0d-4be5-873d-b49d1dee35fa)]
+interface nsIAccessibleHideEvent: nsIAccessibleEvent
+{
+ /**
+ * Return an accessible that was a parent of the target.
+ */
+ readonly attribute nsIAccessible targetParent;
+
+ /**
+ * Return an accessible that was a next sibling of the target
+ */
+ readonly attribute nsIAccessible targetNextSibling;
+
+ /**
+ * Return an accessible that was a parent of the target
+ */
+ readonly attribute nsIAccessible targetPrevSibling;
+};
diff --git a/accessible/interfaces/nsIAccessibleHyperLink.idl b/accessible/interfaces/nsIAccessibleHyperLink.idl
new file mode 100644
index 000000000..e2926d4f0
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibleHyperLink.idl
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 "nsISupports.idl"
+
+interface nsIURI;
+interface nsIAccessible;
+
+/**
+ * A cross-platform interface that supports hyperlink-specific properties and
+ * methods. Anchors, image maps, xul:labels with class="text-link" implement this interface.
+ */
+[scriptable, builtinclass, uuid(883643d4-93a5-4f32-922c-6f06e01363c1)]
+interface nsIAccessibleHyperLink : nsISupports
+{
+ /**
+ * Returns the offset of the link within the parent accessible.
+ */
+ readonly attribute long startIndex;
+
+ /**
+ * Returns the end index of the link within the parent accessible.
+ *
+ * @note The link itself is represented by one embedded character within the
+ * parent text, so the endIndex should be startIndex + 1.
+ */
+ readonly attribute long endIndex;
+
+ /**
+ * Determines whether the link is valid (e. g. points to a valid URL).
+ *
+ * @note XXX Currently only used with ARIA links, and the author has to
+ * specify that the link is invalid via the aria-invalid="true" attribute.
+ * In all other cases, TRUE is returned.
+ */
+ readonly attribute boolean valid;
+
+ /**
+ * The numbber of anchors within this Hyperlink. Is normally 1 for anchors.
+ * This anchor is, for example, the visible output of the html:a tag.
+ * With an Image Map, reflects the actual areas within the map.
+ */
+ readonly attribute long anchorCount;
+
+ /**
+ * Returns the URI at the given index.
+ *
+ * @note ARIA hyperlinks do not have an URI to point to, since clicks are
+ * processed via JavaScript. Therefore this property does not work on ARIA
+ * links.
+ *
+ * @param index The 0-based index of the URI to be returned.
+ *
+ * @return the nsIURI object containing the specifications for the URI.
+ */
+ nsIURI getURI (in long index);
+
+ /**
+ * Returns a reference to the object at the given index.
+ *
+ * @param index The 0-based index whose object is to be returned.
+ *
+ * @return the nsIAccessible object at the desired index.
+ */
+ nsIAccessible getAnchor (in long index);
+};
+
+/*
+ Assumptions:
+
+ The object associated with object or anchor index
+ is an nsIAccessible.
+ A URI can be represented by the nsIURI interface
+ (or nsIURL interface).
+
+ Note that an object which supports nsIAccessibleHyperlink
+ does *not* generally implement nsIAccessible, unlike the
+ case of the other nsiAccessible* interfaces in this directory.
+
+ Aaron: would the nsISupports return from
+ getObject be queryable for nsIURI and nsIURL directly?
+
+*/
diff --git a/accessible/interfaces/nsIAccessibleHyperText.idl b/accessible/interfaces/nsIAccessibleHyperText.idl
new file mode 100644
index 000000000..6e34f2a90
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibleHyperText.idl
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 "nsISupports.idl"
+#include "nsIAccessibleHyperLink.idl"
+
+/**
+ * A cross-platform interface that deals with text which contains hyperlinks.
+ * Each link is an embedded object representing exactly 1 character within
+ * the hypertext.
+ *
+ * Current implementation assumes every embedded object is a link.
+ */
+
+[scriptable, builtinclass, uuid(b33684e2-090c-4e1d-a3d9-f4b46f4237b9)]
+interface nsIAccessibleHyperText : nsISupports
+{
+ /**
+ * Return the number of links contained within this hypertext object.
+ */
+ readonly attribute long linkCount;
+
+ /**
+ * Return link accessible at the given index.
+ *
+ * @param index [in] 0-based index of the link that is to be retrieved
+ *
+ * @return link accessible or null if there is no link at that index
+ */
+ nsIAccessibleHyperLink getLinkAt(in long index);
+
+ /**
+ * Return index of the given link.
+ *
+ * @param link [in] link accessible the index is requested for
+ *
+ * @return index of the given link or null if there's no link within
+ * hypertext accessible
+ */
+ long getLinkIndex(in nsIAccessibleHyperLink link);
+
+ /*
+ * Return link index at the given offset within hypertext accessible.
+ *
+ * @param offset [in] the 0-based character index
+ *
+ * @return 0-based link's index or -1 if no link is present at that
+ * offset
+ */
+ long getLinkIndexAtOffset(in long offset);
+};
diff --git a/accessible/interfaces/nsIAccessibleImage.idl b/accessible/interfaces/nsIAccessibleImage.idl
new file mode 100644
index 000000000..d9980c0cc
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibleImage.idl
@@ -0,0 +1,31 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* 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 "nsISupports.idl"
+
+[scriptable, builtinclass, uuid(09086623-0f09-4310-ac56-c2cda7c29648)]
+interface nsIAccessibleImage : nsISupports
+{
+ /**
+ * Returns the coordinates of the image.
+ *
+ * @param coordType specifies coordinates origin (for available constants
+ * refer to nsIAccessibleCoordinateType)
+ * @param x the x coordinate
+ * @param y the y coordinate
+ */
+ void getImagePosition(in unsigned long coordType,
+ out long x,
+ out long y);
+
+ /**
+ * Returns the size of the image.
+ *
+ * @param width the heigth
+ * @param height the width
+ */
+ void getImageSize(out long width, out long height);
+};
+
diff --git a/accessible/interfaces/nsIAccessibleObjectAttributeChangedEvent.idl b/accessible/interfaces/nsIAccessibleObjectAttributeChangedEvent.idl
new file mode 100644
index 000000000..3038599e4
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibleObjectAttributeChangedEvent.idl
@@ -0,0 +1,21 @@
+/* -*- 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 "nsIAccessibleEvent.idl"
+
+interface nsIAtom;
+
+/**
+ * Fired when an attribute of an accessible changes.
+ */
+[scriptable, builtinclass, uuid(ce41add2-096e-4606-b1ca-7408c6d5b4c3)]
+interface nsIAccessibleObjectAttributeChangedEvent : nsIAccessibleEvent
+{
+ /**
+ * Return the accessible attribute that changed.
+ */
+ readonly attribute nsIAtom changedAttribute;
+};
diff --git a/accessible/interfaces/nsIAccessiblePivot.idl b/accessible/interfaces/nsIAccessiblePivot.idl
new file mode 100644
index 000000000..76d2bfd75
--- /dev/null
+++ b/accessible/interfaces/nsIAccessiblePivot.idl
@@ -0,0 +1,259 @@
+/* -*- 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 "nsISupports.idl"
+
+typedef short TextBoundaryType;
+typedef short PivotMoveReason;
+
+interface nsIAccessible;
+interface nsIAccessibleText;
+interface nsIAccessibleTraversalRule;
+interface nsIAccessiblePivotObserver;
+
+/**
+ * The pivot interface encapsulates a reference to a single place in an accessible
+ * subtree. The pivot is a point or a range in the accessible tree. This interface
+ * provides traversal methods to move the pivot to next/prev state that complies
+ * to a given rule.
+ */
+[scriptable, uuid(81fe5144-059b-42db-bd3a-f6ce3158d5e9)]
+interface nsIAccessiblePivot : nsISupports
+{
+ const TextBoundaryType CHAR_BOUNDARY = 0;
+ const TextBoundaryType WORD_BOUNDARY = 1;
+ const TextBoundaryType LINE_BOUNDARY = 2;
+ const TextBoundaryType ATTRIBUTE_RANGE_BOUNDARY = 3;
+
+ const PivotMoveReason REASON_NONE = 0;
+ const PivotMoveReason REASON_NEXT = 1;
+ const PivotMoveReason REASON_PREV = 2;
+ const PivotMoveReason REASON_FIRST = 3;
+ const PivotMoveReason REASON_LAST = 4;
+ const PivotMoveReason REASON_TEXT = 5;
+ const PivotMoveReason REASON_POINT = 6;
+
+ /**
+ * The accessible the pivot is currently pointed at.
+ */
+ attribute nsIAccessible position;
+
+ /**
+ * The root of the subtree in which the pivot traverses.
+ */
+ readonly attribute nsIAccessible root;
+
+ /**
+ * The temporary modal root to which traversal is limited to.
+ */
+ attribute nsIAccessible modalRoot;
+
+ /**
+ * The start offset of the text range the pivot points at, otherwise -1.
+ */
+ readonly attribute long startOffset;
+
+ /**
+ * The end offset of the text range the pivot points at, otherwise -1.
+ */
+ readonly attribute long endOffset;
+
+ /**
+ * Set the pivot's text range in a text accessible.
+ *
+ * @param aTextAccessible [in] the text accessible that contains the desired
+ * range.
+ * @param aStartOffset [in] the start offset to set.
+ * @param aEndOffset [in] the end offset to set.
+ * @param aIsFromUserInput [in] the pivot changed because of direct user input
+ * (default is true).
+ * @throws NS_ERROR_INVALID_ARG when the offset exceeds the accessible's
+ * character count.
+ */
+ [optional_argc] void setTextRange(in nsIAccessibleText aTextAccessible,
+ in long aStartOffset, in long aEndOffset,
+ [optional] in boolean aIsFromUserInput);
+
+ /**
+ * Move pivot to next object, from current position or given anchor,
+ * complying to given traversal rule.
+ *
+ * @param aRule [in] traversal rule to use.
+ * @param aAnchor [in] accessible to start search from, if not provided,
+ * current position will be used.
+ * @param aIncludeStart [in] include anchor accessible in search.
+ * @param aIsFromUserInput [in] the pivot changed because of direct user input
+ * (default is true).
+ * @return true on success, false if there are no new nodes to traverse to.
+ */
+ [optional_argc] boolean moveNext(in nsIAccessibleTraversalRule aRule,
+ [optional] in nsIAccessible aAnchor,
+ [optional] in boolean aIncludeStart,
+ [optional] in boolean aIsFromUserInput);
+
+ /**
+ * Move pivot to previous object, from current position or given anchor,
+ * complying to given traversal rule.
+ *
+ * @param aRule [in] traversal rule to use.
+ * @param aAnchor [in] accessible to start search from, if not provided,
+ * current position will be used.
+ * @param aIncludeStart [in] include anchor accessible in search.
+ * @param aIsFromUserInput [in] the pivot changed because of direct user input
+ * (default is true).
+ * @return true on success, false if there are no new nodes to traverse to.
+ */
+ [optional_argc] boolean movePrevious(in nsIAccessibleTraversalRule aRule,
+ [optional] in nsIAccessible aAnchor,
+ [optional] in boolean aIncludeStart,
+ [optional] in boolean aIsFromUserInput);
+
+ /**
+ * Move pivot to first object in subtree complying to given traversal rule.
+ *
+ * @param aRule [in] traversal rule to use.
+ * @param aIsFromUserInput [in] the pivot changed because of direct user input
+ * (default is true).
+ * @return true on success, false if there are no new nodes to traverse to.
+ */
+ [optional_argc] boolean moveFirst(in nsIAccessibleTraversalRule aRule,
+ [optional] in boolean aIsFromUserInput);
+
+ /**
+ * Move pivot to last object in subtree complying to given traversal rule.
+ *
+ * @param aRule [in] traversal rule to use.
+ * @param aIsFromUserInput [in] the pivot changed because of direct user input
+ * (default is true).
+ */
+ [optional_argc] boolean moveLast(in nsIAccessibleTraversalRule aRule,
+ [optional] in boolean aIsFromUserInput);
+
+ /**
+ * Move pivot to next text range.
+ *
+ * @param aBoundary [in] type of boundary for next text range,
+ * character, word, etc.
+ * @param aIsFromUserInput [in] the pivot changed because of direct user input
+ * (default is true).
+ * @return true on success, false if there are is no more text.
+ */
+ [optional_argc] boolean moveNextByText(in TextBoundaryType aBoundary,
+ [optional] in boolean aIsFromUserInput);
+
+ /**
+ * Move pivot to previous text range.
+ *
+ * @param aBoundary [in] type of boundary for next text range,
+ * character, word, etc.
+ * @param aIsFromUserInput [in] the pivot changed because of direct user input
+ * (default is true).
+ * @return true on success, false if there are is no more text.
+ */
+ [optional_argc] boolean movePreviousByText(in TextBoundaryType aBoundary,
+ [optional] in boolean aIsFromUserInput);
+
+ /**
+ * Move pivot to given coordinate in screen pixels.
+ *
+ * @param aRule [in] raversal rule to use.
+ * @param aX [in] screen's x coordinate
+ * @param aY [in] screen's y coordinate
+ * @param aIgnoreNoMatch [in] don't unset position if no object was found
+ * at point.
+ * @param aIsFromUserInput [in] the pivot changed because of direct user input
+ * (default is true).
+ * @return true on success, false if the pivot has not been moved.
+ */
+ [optional_argc] boolean moveToPoint(in nsIAccessibleTraversalRule aRule,
+ in long aX, in long aY,
+ in boolean aIgnoreNoMatch,
+ [optional] in boolean aIsFromUserInput);
+
+ /**
+ * Add an observer for pivot changes.
+ *
+ * @param aObserver [in] the observer object to be notified of pivot changes.
+ */
+ void addObserver(in nsIAccessiblePivotObserver aObserver);
+
+ /**
+ * Remove an observer for pivot changes.
+ *
+ * @param aObserver [in] the observer object to remove from being notified.
+ */
+ void removeObserver(in nsIAccessiblePivotObserver aObserver);
+};
+
+/**
+ * An observer interface for pivot changes.
+ */
+[scriptable, uuid(6006e502-3861-49bd-aba1-fa6d2e74e237)]
+interface nsIAccessiblePivotObserver : nsISupports
+{
+ /**
+ * Called when the pivot changes.
+ *
+ * @param aPivot [in] the pivot that has changed.
+ * @param aOldAccessible [in] the old pivot position before the change,
+ * or null.
+ * @param aOldStart [in] the old start offset, or -1.
+ * @param aOldEnd [in] the old end offset, or -1.
+ * @param aReason [in] the reason for the pivot change.
+ * @param aIsFromUserInput [in] the pivot changed because of direct user input
+ * (default is true).
+ */
+ void onPivotChanged(in nsIAccessiblePivot aPivot,
+ in nsIAccessible aOldAccessible,
+ in long aOldStart, in long aOldEnd,
+ in PivotMoveReason aReason,
+ in boolean aIsFromUserInput);
+};
+
+[scriptable, uuid(e197460d-1eff-4247-b4bb-a43be1840dae)]
+interface nsIAccessibleTraversalRule : nsISupports
+{
+ /* Ignore this accessible object */
+ const unsigned short FILTER_IGNORE = 0x0;
+ /* Accept this accessible object */
+ const unsigned short FILTER_MATCH = 0x1;
+ /* Don't traverse accessibles children */
+ const unsigned short FILTER_IGNORE_SUBTREE = 0x2;
+
+ /* Pre-filters */
+ const unsigned long PREFILTER_INVISIBLE = 0x00000001;
+ const unsigned long PREFILTER_OFFSCREEN = 0x00000002;
+ const unsigned long PREFILTER_NOT_FOCUSABLE = 0x00000004;
+ const unsigned long PREFILTER_ARIA_HIDDEN = 0x00000008;
+ const unsigned long PREFILTER_TRANSPARENT = 0x00000010;
+
+ /**
+ * Pre-filter bitfield to filter out obviously ignorable nodes and lighten
+ * the load on match().
+ */
+ readonly attribute unsigned long preFilter;
+
+ /**
+ * Retrieve a list of roles that the traversal rule should test for. Any node
+ * with a role not in this list will automatically be ignored. An empty list
+ * will match all roles. It should be assumed that this method is called once
+ * at the start of a traversal, so changing the method's return result after
+ * that would have no affect.
+ *
+ * @param aRoles [out] an array of the roles to match.
+ * @param aCount [out] the length of the array.
+ */
+ void getMatchRoles([array, size_is(aCount)]out unsigned long aRoles,
+ [retval]out unsigned long aCount);
+
+ /**
+ * Determines if a given accessible is to be accepted in our traversal rule
+ *
+ * @param aAccessible [in] accessible to examine.
+ * @return a bitfield of FILTER_MATCH and FILTER_IGNORE_SUBTREE.
+ */
+ unsigned short match(in nsIAccessible aAccessible);
+};
diff --git a/accessible/interfaces/nsIAccessibleRelation.idl b/accessible/interfaces/nsIAccessibleRelation.idl
new file mode 100644
index 000000000..de51d5a0a
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibleRelation.idl
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* 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 "nsISupports.idl"
+#include "nsIArray.idl"
+
+interface nsIAccessible;
+
+/**
+ * This interface gives access to an accessible's set of relations.
+ */
+[scriptable, builtinclass, uuid(55b308c4-2ae4-46bc-b4cd-4d4370e0a660)]
+interface nsIAccessibleRelation : nsISupports
+{
+ /**
+ * This object is labelled by a target object.
+ */
+ const unsigned long RELATION_LABELLED_BY = 0x00;
+
+ /**
+ * This object is label for a target object.
+ */
+ const unsigned long RELATION_LABEL_FOR = 0x01;
+
+ /**
+ * This object is described by the target object.
+ */
+ const unsigned long RELATION_DESCRIBED_BY = 0x02;
+
+ /**
+ * This object is describes the target object.
+ */
+ const unsigned long RELATION_DESCRIPTION_FOR = 0x3;
+
+ /**
+ * This object is a child of a target object.
+ */
+ const unsigned long RELATION_NODE_CHILD_OF = 0x4;
+
+ /**
+ * This object is a parent of a target object. A dual relation to
+ * RELATION_NODE_CHILD_OF
+ */
+ const unsigned long RELATION_NODE_PARENT_OF = 0x5;
+
+ /**
+ * Some attribute of this object is affected by a target object.
+ */
+ const unsigned long RELATION_CONTROLLED_BY = 0x06;
+
+ /**
+ * This object is interactive and controls some attribute of a target object.
+ */
+ const unsigned long RELATION_CONTROLLER_FOR = 0x07;
+
+ /**
+ * Content flows from this object to a target object, i.e. has content that
+ * flows logically to another object in a sequential way, e.g. text flow.
+ */
+ const unsigned long RELATION_FLOWS_TO = 0x08;
+
+ /**
+ * Content flows to this object from a target object, i.e. has content that
+ * flows logically from another object in a sequential way, e.g. text flow.
+ */
+ const unsigned long RELATION_FLOWS_FROM = 0x09;
+
+ /**
+ * This object is a member of a group of one or more objects. When there is
+ * more than one object in the group each member may have one and the same
+ * target, e.g. a grouping object. It is also possible that each member has
+ * multiple additional targets, e.g. one for every other member in the group.
+ */
+ const unsigned long RELATION_MEMBER_OF = 0x0a;
+
+ /**
+ * This object is a sub window of a target object.
+ */
+ const unsigned long RELATION_SUBWINDOW_OF = 0x0b;
+
+ /**
+ * This object embeds a target object. This relation can be used on the
+ * OBJID_CLIENT accessible for a top level window to show where the content
+ * areas are.
+ */
+ const unsigned long RELATION_EMBEDS = 0x0c;
+
+ /**
+ * This object is embedded by a target object.
+ */
+ const unsigned long RELATION_EMBEDDED_BY = 0x0d;
+
+ /**
+ * This object is a transient component related to the target object. When
+ * this object is activated the target object doesn't lose focus.
+ */
+ const unsigned long RELATION_POPUP_FOR = 0x0e;
+
+ /**
+ * This object is a parent window of the target object.
+ */
+ const unsigned long RELATION_PARENT_WINDOW_OF = 0x0f;
+
+ /**
+ * Part of a form/dialog with a related default button. It is used for
+ * MSAA/XPCOM, it isn't for IA2 or ATK.
+ */
+ const unsigned long RELATION_DEFAULT_BUTTON = 0x10;
+
+ /**
+ * The target object is the containing document object.
+ */
+ const unsigned long RELATION_CONTAINING_DOCUMENT = 0x11;
+
+ /**
+ * The target object is the topmost containing document object in the tab pane.
+ */
+ const unsigned long RELATION_CONTAINING_TAB_PANE = 0x12;
+
+ /**
+ * The target object is the containing application object.
+ */
+ const unsigned long RELATION_CONTAINING_APPLICATION = 0x14;
+
+ /**
+ * The target object provides the detailed, extended description for this
+ * object. It provides more detailed information than would normally be
+ * provided using the DESCRIBED_BY relation. A common use for this relation is
+ * in digital publishing where an extended description needs to be conveyed in
+ * a book that requires structural markup or the embedding of other technology
+ * to provide illustrative content.
+ */
+ const unsigned long RELATION_DETAILS = 0x15;
+
+ /**
+ * This object provides the detailed, extended description for the target
+ * object. See DETAILS relation.
+ */
+ const unsigned long RELATION_DETAILS_FOR = 0x16;
+
+ /**
+ * The target object is the error message for this object.
+ */
+ const unsigned long RELATION_ERRORMSG = 0x17;
+
+ /**
+ * This object is the error message for the target object.
+ */
+ const unsigned long RELATION_ERRORMSG_FOR = 0x18;
+
+ /**
+ * Returns the type of the relation.
+ */
+ readonly attribute unsigned long relationType;
+
+ /**
+ * Returns the number of targets for this relation.
+ */
+ readonly attribute unsigned long targetsCount;
+
+ /**
+ * Returns one accessible relation target.
+ * @param index - 0 based index of relation target.
+ */
+ nsIAccessible getTarget(in unsigned long index);
+
+ /**
+ * Returns multiple accessible relation targets.
+ */
+ nsIArray getTargets();
+};
diff --git a/accessible/interfaces/nsIAccessibleRole.idl b/accessible/interfaces/nsIAccessibleRole.idl
new file mode 100644
index 000000000..16b379d60
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibleRole.idl
@@ -0,0 +1,980 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* 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 "nsISupports.idl"
+
+/**
+ * Defines cross platform (Gecko) roles.
+ */
+[scriptable, builtinclass, uuid(05a9f33f-dcfd-4e7b-b825-138ba784c1f5)]
+interface nsIAccessibleRole : nsISupports
+{
+ /**
+ * Used when accessible hans't strong defined role.
+ */
+ const unsigned long ROLE_NOTHING = 0;
+
+ /**
+ * Represents a title or caption bar for a window. It is used by MSAA only,
+ * supported automatically by MS Windows.
+ */
+ const unsigned long ROLE_TITLEBAR = 1;
+
+ /**
+ * Represents the menu bar (positioned beneath the title bar of a window)
+ * from which menus are selected by the user. The role is used by
+ * xul:menubar or role="menubar".
+ */
+ const unsigned long ROLE_MENUBAR = 2;
+
+ /**
+ * Represents a vertical or horizontal scroll bar, which is part of the client
+ * area or used in a control.
+ */
+ const unsigned long ROLE_SCROLLBAR = 3;
+
+ /**
+ * Represents a special mouse pointer, which allows a user to manipulate user
+ * interface elements such as windows. For example, a user clicks and drags
+ * a sizing grip in the lower-right corner of a window to resize it.
+ */
+ const unsigned long ROLE_GRIP = 4;
+
+ /**
+ * Represents a system sound, which is associated with various system events.
+ */
+ const unsigned long ROLE_SOUND = 5;
+
+ /**
+ * Represents the system mouse pointer.
+ */
+ const unsigned long ROLE_CURSOR = 6;
+
+ /**
+ * Represents the system caret. The role is supported for caret.
+ */
+ const unsigned long ROLE_CARET = 7;
+
+ /**
+ * Represents an alert or a condition that a user should be notified about.
+ * Assistive Technologies typically respond to the role by reading the entire
+ * onscreen contents of containers advertising this role. Should be used for
+ * warning dialogs, etc. The role is used by xul:browsermessage,
+ * role="alert".
+ */
+ const unsigned long ROLE_ALERT = 8;
+
+ /**
+ * Represents the window frame, which contains child objects such as
+ * a title bar, client, and other objects contained in a window. The role
+ * is supported automatically by MS Windows.
+ */
+ const unsigned long ROLE_WINDOW = 9;
+
+ /**
+ * A sub-document (<frame> or <iframe>)
+ */
+ const unsigned long ROLE_INTERNAL_FRAME = 10;
+
+ /**
+ * Represents a menu, which presents a list of options from which the user can
+ * make a selection to perform an action. It is used for role="menu".
+ */
+ const unsigned long ROLE_MENUPOPUP = 11;
+
+ /**
+ * Represents a menu item, which is an entry in a menu that a user can choose
+ * to carry out a command, select an option. It is used for xul:menuitem,
+ * role="menuitem".
+ */
+ const unsigned long ROLE_MENUITEM = 12;
+
+ /**
+ * Represents a ToolTip that provides helpful hints.
+ */
+ const unsigned long ROLE_TOOLTIP = 13;
+
+ /**
+ * Represents a main window for an application. It is used for
+ * role="application". Also refer to ROLE_APP_ROOT
+ */
+ const unsigned long ROLE_APPLICATION = 14;
+
+ /**
+ * Represents a document window. A document window is always contained within
+ * an application window. It is used for role="document".
+ */
+ const unsigned long ROLE_DOCUMENT = 15;
+
+ /**
+ * Represents a pane within a frame or document window. Users can navigate
+ * between panes and within the contents of the current pane, but cannot
+ * navigate between items in different panes. Thus, panes represent a level
+ * of grouping lower than frame windows or documents, but above individual
+ * controls. It is used for the first child of a <frame> or <iframe>.
+ */
+ const unsigned long ROLE_PANE = 16;
+
+ /**
+ * Represents a graphical image used to represent data.
+ */
+ const unsigned long ROLE_CHART = 17;
+
+ /**
+ * Represents a dialog box or message box. It is used for xul:dialog,
+ * role="dialog".
+ */
+ const unsigned long ROLE_DIALOG = 18;
+
+ /**
+ * Represents a window border.
+ */
+ const unsigned long ROLE_BORDER = 19;
+
+ /**
+ * Logically groups other objects. There is not always a parent-child
+ * relationship between the grouping object and the objects it contains. It
+ * is used for html:textfield, xul:groupbox, role="group".
+ */
+ const unsigned long ROLE_GROUPING = 20;
+
+ /**
+ * Used to visually divide a space into two regions, such as a separator menu
+ * item or a bar that divides split panes within a window. It is used for
+ * xul:separator, html:hr, role="separator".
+ */
+ const unsigned long ROLE_SEPARATOR = 21;
+
+ /**
+ * Represents a toolbar, which is a grouping of controls (push buttons or
+ * toggle buttons) that provides easy access to frequently used features. It
+ * is used for xul:toolbar, role="toolbar".
+ */
+ const unsigned long ROLE_TOOLBAR = 22;
+
+ /**
+ * Represents a status bar, which is an area at the bottom of a window that
+ * displays information about the current operation, state of the application,
+ * or selected object. The status bar has multiple fields, which display
+ * different kinds of information. It is used for xul:statusbar.
+ */
+ const unsigned long ROLE_STATUSBAR = 23;
+
+ /**
+ * Represents a table that contains rows and columns of cells, and optionally,
+ * row headers and column headers. It is used for html:table,
+ * role="grid". Also refer to the following roles: ROLE_COLUMNHEADER,
+ * ROLE_ROWHEADER, ROLE_COLUMN, ROLE_ROW, ROLE_CELL.
+ */
+ const unsigned long ROLE_TABLE = 24;
+
+ /**
+ * Represents a column header, providing a visual label for a column in
+ * a table. It is used for XUL tree column headers, html:th,
+ * role="colheader". Also refer to ROLE_TABLE.
+ */
+ const unsigned long ROLE_COLUMNHEADER = 25;
+
+ /**
+ * Represents a row header, which provides a visual label for a table row.
+ * It is used for role="rowheader". Also, see ROLE_TABLE.
+ */
+ const unsigned long ROLE_ROWHEADER = 26;
+
+ /**
+ * Represents a column of cells within a table. Also, see ROLE_TABLE.
+ */
+ const unsigned long ROLE_COLUMN = 27;
+
+ /**
+ * Represents a row of cells within a table. Also, see ROLE_TABLE.
+ */
+ const unsigned long ROLE_ROW = 28;
+
+ /**
+ * Represents a cell within a table. It is used for html:td,
+ * xul:tree cell and xul:listcell. Also, see ROLE_TABLE.
+ */
+ const unsigned long ROLE_CELL = 29;
+
+ /**
+ * Represents a link to something else. This object might look like text or
+ * a graphic, but it acts like a button. It is used for
+ * xul:label@class="text-link", html:a, html:area.
+ */
+ const unsigned long ROLE_LINK = 30;
+
+ /**
+ * Displays a Help topic in the form of a ToolTip or Help balloon.
+ */
+ const unsigned long ROLE_HELPBALLOON = 31;
+
+ /**
+ * Represents a cartoon-like graphic object, such as Microsoft Office
+ * Assistant, which is displayed to provide help to users of an application.
+ */
+ const unsigned long ROLE_CHARACTER = 32;
+
+ /**
+ * Represents a list box, allowing the user to select one or more items. It
+ * is used for xul:listbox, html:select@size, role="list". See also
+ * ROLE_LIST_ITEM.
+ */
+ const unsigned long ROLE_LIST = 33;
+
+ /**
+ * Represents an item in a list. See also ROLE_LIST.
+ */
+ const unsigned long ROLE_LISTITEM = 34;
+
+ /**
+ * Represents an outline or tree structure, such as a tree view control,
+ * that displays a hierarchical list and allows the user to expand and
+ * collapse branches. Is is used for role="tree".
+ */
+ const unsigned long ROLE_OUTLINE = 35;
+
+ /**
+ * Represents an item in an outline or tree structure. It is used for
+ * role="treeitem".
+ */
+ const unsigned long ROLE_OUTLINEITEM = 36;
+
+ /**
+ * Represents a page tab, it is a child of a page tab list. It is used for
+ * xul:tab, role="treeitem". Also refer to ROLE_PAGETABLIST.
+ */
+ const unsigned long ROLE_PAGETAB = 37;
+
+ /**
+ * Represents a property sheet. It is used for xul:tabpanel,
+ * role="tabpanel".
+ */
+ const unsigned long ROLE_PROPERTYPAGE = 38;
+
+ /**
+ * Represents an indicator, such as a pointer graphic, that points to the
+ * current item.
+ */
+ const unsigned long ROLE_INDICATOR = 39;
+
+ /**
+ * Represents a picture. Is is used for xul:image, html:img.
+ */
+ const unsigned long ROLE_GRAPHIC = 40;
+
+ /**
+ * Represents read-only text, such as labels for other controls or
+ * instructions in a dialog box. Static text cannot be modified or selected.
+ * Is is used for xul:label, xul:description, html:label, role="label".
+ */
+ const unsigned long ROLE_STATICTEXT = 41;
+
+ /**
+ * Represents selectable text that allows edits or is designated read-only.
+ */
+ const unsigned long ROLE_TEXT_LEAF = 42;
+
+ /**
+ * Represents a push button control. It is used for xul:button, html:button,
+ * role="button".
+ */
+ const unsigned long ROLE_PUSHBUTTON = 43;
+
+ /**
+ * Represents a check box control. It is used for xul:checkbox,
+ * html:input@type="checkbox", role="checkbox".
+ */
+ const unsigned long ROLE_CHECKBUTTON = 44;
+
+ /**
+ * Represents an option button, also called a radio button. It is one of a
+ * group of mutually exclusive options. All objects sharing a single parent
+ * that have this attribute are assumed to be part of single mutually
+ * exclusive group. It is used for xul:radio, html:input@type="radio",
+ * role="radio".
+ */
+ const unsigned long ROLE_RADIOBUTTON = 45;
+
+ /**
+ * Represents a combo box; an edit control with an associated list box that
+ * provides a set of predefined choices. It is used for html:select,
+ * xul:menulist, role="combobox".
+ */
+ const unsigned long ROLE_COMBOBOX = 46;
+
+ /**
+ * Represents the calendar control.
+ */
+ const unsigned long ROLE_DROPLIST = 47;
+
+ /**
+ * Represents a progress bar, dynamically showing the user the percent
+ * complete of an operation in progress. It is used for xul:progressmeter,
+ * role="progressbar".
+ */
+ const unsigned long ROLE_PROGRESSBAR = 48;
+
+ /**
+ * Represents a dial or knob whose purpose is to allow a user to set a value.
+ */
+ const unsigned long ROLE_DIAL = 49;
+
+ /**
+ * Represents a hot-key field that allows the user to enter a combination or
+ * sequence of keystrokes.
+ */
+ const unsigned long ROLE_HOTKEYFIELD = 50;
+
+ /**
+ * Represents a slider, which allows the user to adjust a setting in given
+ * increments between minimum and maximum values. It is used by xul:scale,
+ * role="slider".
+ */
+ const unsigned long ROLE_SLIDER = 51;
+
+ /**
+ * Represents a spin box, which is a control that allows the user to increment
+ * or decrement the value displayed in a separate "buddy" control associated
+ * with the spin box. It is used for xul:spinbuttons.
+ */
+ const unsigned long ROLE_SPINBUTTON = 52;
+
+ /**
+ * Represents a graphical image used to diagram data. It is used for svg:svg.
+ */
+ const unsigned long ROLE_DIAGRAM = 53;
+
+ /**
+ * Represents an animation control, which contains content that changes over
+ * time, such as a control that displays a series of bitmap frames.
+ */
+ const unsigned long ROLE_ANIMATION = 54;
+
+ /**
+ * Represents a mathematical equation. It is used by MATHML, where there is a
+ * rich DOM subtree for an equation. Use ROLE_FLAT_EQUATION for <img role="math" alt="[TeX]"/>
+ */
+ const unsigned long ROLE_EQUATION = 55;
+
+ /**
+ * Represents a button that drops down a list of items.
+ */
+ const unsigned long ROLE_BUTTONDROPDOWN = 56;
+
+ /**
+ * Represents a button that drops down a menu.
+ */
+ const unsigned long ROLE_BUTTONMENU = 57;
+
+ /**
+ * Represents a button that drops down a grid. It is used for xul:colorpicker.
+ */
+ const unsigned long ROLE_BUTTONDROPDOWNGRID = 58;
+
+ /**
+ * Represents blank space between other objects.
+ */
+ const unsigned long ROLE_WHITESPACE = 59;
+
+ /**
+ * Represents a container of page tab controls. Is it used for xul:tabs,
+ * DHTML: role="tabs". Also refer to ROLE_PAGETAB.
+ */
+ const unsigned long ROLE_PAGETABLIST = 60;
+
+ /**
+ * Represents a control that displays time.
+ */
+ const unsigned long ROLE_CLOCK = 61;
+
+ /**
+ * Represents a button on a toolbar that has a drop-down list icon directly
+ * adjacent to the button.
+ */
+ const unsigned long ROLE_SPLITBUTTON = 62;
+
+ /**
+ * Represents an edit control designed for an Internet Protocol (IP) address.
+ * The edit control is divided into sections for the different parts of the
+ * IP address.
+ */
+ const unsigned long ROLE_IPADDRESS = 63;
+
+ /**
+ * Represents a label control that has an accelerator.
+ */
+ const unsigned long ROLE_ACCEL_LABEL = 64;
+
+ /**
+ * Represents an arrow in one of the four cardinal directions.
+ */
+ const unsigned long ROLE_ARROW = 65;
+
+ /**
+ * Represents a control that can be drawn into and is used to trap events.
+ * It is used for html:canvas.
+ */
+ const unsigned long ROLE_CANVAS = 66;
+
+ /**
+ * Represents a menu item with a check box.
+ */
+ const unsigned long ROLE_CHECK_MENU_ITEM = 67;
+
+ /**
+ * Represents a specialized dialog that lets the user choose a color.
+ */
+ const unsigned long ROLE_COLOR_CHOOSER = 68;
+
+ /**
+ * Represents control whose purpose is to allow a user to edit a date.
+ */
+ const unsigned long ROLE_DATE_EDITOR = 69;
+
+ /**
+ * An iconified internal frame in an ROLE_DESKTOP_PANE. Also refer to
+ * ROLE_INTERNAL_FRAME.
+ */
+ const unsigned long ROLE_DESKTOP_ICON = 70;
+
+ /**
+ * A desktop pane. A pane that supports internal frames and iconified
+ * versions of those internal frames.
+ */
+ const unsigned long ROLE_DESKTOP_FRAME = 71;
+
+ /**
+ * A directory pane. A pane that allows the user to navigate through
+ * and select the contents of a directory. May be used by a file chooser.
+ * Also refer to ROLE_FILE_CHOOSER.
+ */
+ const unsigned long ROLE_DIRECTORY_PANE = 72;
+
+ /**
+ * A file chooser. A specialized dialog that displays the files in the
+ * directory and lets the user select a file, browse a different directory,
+ * or specify a filename. May use the directory pane to show the contents of
+ * a directory. Also refer to ROLE_DIRECTORY_PANE.
+ */
+ const unsigned long ROLE_FILE_CHOOSER = 73;
+
+ /**
+ * A font chooser. A font chooser is a component that lets the user pick
+ * various attributes for fonts.
+ */
+ const unsigned long ROLE_FONT_CHOOSER = 74;
+
+ /**
+ * Frame role. A top level window with a title bar, border, menu bar, etc.
+ * It is often used as the primary window for an application.
+ */
+ const unsigned long ROLE_CHROME_WINDOW = 75;
+
+ /**
+ * A glass pane. A pane that is guaranteed to be painted on top of all
+ * panes beneath it. Also refer to ROLE_ROOT_PANE.
+ */
+ const unsigned long ROLE_GLASS_PANE = 76;
+
+ /**
+ * A document container for HTML, whose children represent the document
+ * content.
+ */
+ const unsigned long ROLE_HTML_CONTAINER = 77;
+
+ /**
+ * A small fixed size picture, typically used to decorate components.
+ */
+ const unsigned long ROLE_ICON = 78;
+
+ /**
+ * Presents an icon or short string in an interface.
+ */
+ const unsigned long ROLE_LABEL = 79;
+
+ /**
+ * A layered pane. A specialized pane that allows its children to be drawn
+ * in layers, providing a form of stacking order. This is usually the pane
+ * that holds the menu bar as well as the pane that contains most of the
+ * visual components in a window. Also refer to ROLE_GLASS_PANE and
+ * ROLE_ROOT_PANE.
+ */
+ const unsigned long ROLE_LAYERED_PANE = 80;
+
+ /**
+ * A specialized pane whose primary use is inside a dialog.
+ */
+ const unsigned long ROLE_OPTION_PANE = 81;
+
+ /**
+ * A text object uses for passwords, or other places where the text content
+ * is not shown visibly to the user.
+ */
+ const unsigned long ROLE_PASSWORD_TEXT = 82;
+
+ /**
+ * A temporary window that is usually used to offer the user a list of
+ * choices, and then hides when the user selects one of those choices.
+ */
+ const unsigned long ROLE_POPUP_MENU = 83;
+
+ /**
+ * A radio button that is a menu item.
+ */
+ const unsigned long ROLE_RADIO_MENU_ITEM = 84;
+
+ /**
+ * A root pane. A specialized pane that has a glass pane and a layered pane
+ * as its children. Also refer to ROLE_GLASS_PANE and ROLE_LAYERED_PANE.
+ */
+ const unsigned long ROLE_ROOT_PANE = 85;
+
+ /**
+ * A scroll pane. An object that allows a user to incrementally view a large
+ * amount of information. Its children can include scroll bars and a
+ * viewport. Also refer to ROLE_VIEW_PORT.
+ */
+ const unsigned long ROLE_SCROLL_PANE = 86;
+
+ /**
+ * A split pane. A specialized panel that presents two other panels at the
+ * same time. Between the two panels is a divider the user can manipulate to
+ * make one panel larger and the other panel smaller.
+ */
+ const unsigned long ROLE_SPLIT_PANE = 87;
+
+ /**
+ * The header for a column of a table.
+ * XXX: it looks this role is dupe of ROLE_COLUMNHEADER.
+ */
+ const unsigned long ROLE_TABLE_COLUMN_HEADER = 88;
+
+ /**
+ * The header for a row of a table.
+ * XXX: it looks this role is dupe of ROLE_ROWHEADER
+ */
+ const unsigned long ROLE_TABLE_ROW_HEADER = 89;
+
+ /**
+ * A menu item used to tear off and reattach its menu.
+ */
+ const unsigned long ROLE_TEAR_OFF_MENU_ITEM = 90;
+
+ /**
+ * Represents an accessible terminal.
+ */
+ const unsigned long ROLE_TERMINAL = 91;
+
+ /**
+ * Collection of objects that constitute a logical text entity.
+ */
+ const unsigned long ROLE_TEXT_CONTAINER = 92;
+
+ /**
+ * A toggle button. A specialized push button that can be checked or
+ * unchecked, but does not provide a separate indicator for the current state.
+ */
+ const unsigned long ROLE_TOGGLE_BUTTON = 93;
+
+ /**
+ * Representas a control that is capable of expanding and collapsing rows as
+ * well as showing multiple columns of data.
+ * XXX: it looks like this role is dupe of ROLE_OUTLINE.
+ */
+ const unsigned long ROLE_TREE_TABLE = 94;
+
+ /**
+ * A viewport. An object usually used in a scroll pane. It represents the
+ * portion of the entire data that the user can see. As the user manipulates
+ * the scroll bars, the contents of the viewport can change. Also refer to
+ * ROLE_SCROLL_PANE.
+ */
+ const unsigned long ROLE_VIEWPORT = 95;
+
+ /**
+ * Header of a document page. Also refer to ROLE_FOOTER.
+ */
+ const unsigned long ROLE_HEADER = 96;
+
+ /**
+ * Footer of a document page. Also refer to ROLE_HEADER.
+ */
+ const unsigned long ROLE_FOOTER = 97;
+
+ /**
+ * A paragraph of text.
+ */
+ const unsigned long ROLE_PARAGRAPH = 98;
+
+ /**
+ * A ruler such as those used in word processors.
+ */
+ const unsigned long ROLE_RULER = 99;
+
+ /**
+ * A text entry having dialog or list containing items for insertion into
+ * an entry widget, for instance a list of words for completion of a
+ * text entry. It is used for xul:textbox@autocomplete
+ */
+ const unsigned long ROLE_AUTOCOMPLETE = 100;
+
+ /**
+ * An editable text object in a toolbar.
+ */
+ const unsigned long ROLE_EDITBAR = 101;
+
+ /**
+ * An control whose textual content may be entered or modified by the user.
+ */
+ const unsigned long ROLE_ENTRY = 102;
+
+ /**
+ * A caption describing another object.
+ */
+ const unsigned long ROLE_CAPTION = 103;
+
+ /**
+ * A visual frame or container which contains a view of document content.
+ * Document frames may occur within another Document instance, in which case
+ * the second document may be said to be embedded in the containing instance.
+ * HTML frames are often ROLE_DOCUMENT_FRAME. Either this object, or a
+ * singleton descendant, should implement the Document interface.
+ */
+ const unsigned long ROLE_DOCUMENT_FRAME = 104;
+
+ /**
+ * Heading.
+ */
+ const unsigned long ROLE_HEADING = 105;
+
+ /**
+ * An object representing a page of document content. It is used in documents
+ * which are accessed by the user on a page by page basis.
+ */
+ const unsigned long ROLE_PAGE = 106;
+
+ /**
+ * A container of document content. An example of the use of this role is to
+ * represent an html:div.
+ */
+ const unsigned long ROLE_SECTION = 107;
+
+ /**
+ * An object which is redundant with another object in the accessible
+ * hierarchy. ATs typically ignore objects with this role.
+ */
+ const unsigned long ROLE_REDUNDANT_OBJECT = 108;
+
+ /**
+ * A container of form controls. An example of the use of this role is to
+ * represent an html:form.
+ */
+ const unsigned long ROLE_FORM = 109;
+
+ /**
+ * An object which is used to allow input of characters not found on a
+ * keyboard, such as the input of Chinese characters on a Western keyboard.
+ */
+ const unsigned long ROLE_IME = 110;
+
+ /**
+ * XXX: document this.
+ */
+ const unsigned long ROLE_APP_ROOT = 111;
+
+ /**
+ * Represents a menu item, which is an entry in a menu that a user can choose
+ * to display another menu.
+ */
+ const unsigned long ROLE_PARENT_MENUITEM = 112;
+
+ /**
+ * A calendar that allows the user to select a date.
+ */
+ const unsigned long ROLE_CALENDAR = 113;
+
+ /**
+ * A list of items that is shown by combobox.
+ */
+ const unsigned long ROLE_COMBOBOX_LIST = 114;
+
+ /**
+ * A item of list that is shown by combobox;
+ */
+ const unsigned long ROLE_COMBOBOX_OPTION = 115;
+
+ /**
+ * An image map -- has child links representing the areas
+ */
+ const unsigned long ROLE_IMAGE_MAP = 116;
+
+ /**
+ * An option in a listbox
+ */
+ const unsigned long ROLE_OPTION = 117;
+
+ /**
+ * A rich option in a listbox, it can have other widgets as children
+ */
+ const unsigned long ROLE_RICH_OPTION = 118;
+
+ /**
+ * A list of options
+ */
+ const unsigned long ROLE_LISTBOX = 119;
+
+ /**
+ * Represents a mathematical equation in the accessible name
+ */
+ const unsigned long ROLE_FLAT_EQUATION = 120;
+
+ /**
+ * Represents a cell within a grid. It is used for role="gridcell". Unlike
+ * ROLE_CELL, it allows the calculation of the accessible name from subtree.
+ * Also, see ROLE_TABLE.
+ */
+ const unsigned long ROLE_GRID_CELL = 121;
+
+ /**
+ * Represents an embedded object. It is used for html:object or html:embed.
+ */
+ const unsigned long ROLE_EMBEDDED_OBJECT = 122;
+
+ /**
+ * A note. Originally intended to be hidden until activated, but now also used
+ * for things like html 'aside'.
+ */
+ const unsigned long ROLE_NOTE = 123;
+
+ /**
+ * A figure. Used for things like HTML5 figure element.
+ */
+ const unsigned long ROLE_FIGURE = 124;
+
+ /**
+ * Represents a rich item with a check box.
+ */
+ const unsigned long ROLE_CHECK_RICH_OPTION = 125;
+
+ /**
+ * An HTML definition list <dl>
+ */
+ const unsigned long ROLE_DEFINITION_LIST = 126;
+
+ /**
+ * An HTML definition term <dt>
+ */
+ const unsigned long ROLE_TERM = 127;
+
+ /**
+ * An HTML definition <dd>
+ */
+ const unsigned long ROLE_DEFINITION = 128;
+
+ /**
+ * A keyboard or keypad key.
+ */
+ const unsigned long ROLE_KEY = 129;
+
+ /**
+ * A switch control widget.
+ */
+ const unsigned long ROLE_SWITCH = 130;
+
+ /**
+ * A block of MathML code (math).
+ */
+ const unsigned long ROLE_MATHML_MATH = 131;
+
+ /**
+ * A MathML identifier (mi in MathML).
+ */
+ const unsigned long ROLE_MATHML_IDENTIFIER = 132;
+
+ /**
+ * A MathML number (mn in MathML).
+ */
+ const unsigned long ROLE_MATHML_NUMBER = 133;
+
+ /**
+ * A MathML operator (mo in MathML).
+ */
+ const unsigned long ROLE_MATHML_OPERATOR = 134;
+
+ /**
+ * A MathML text (mtext in MathML).
+ */
+ const unsigned long ROLE_MATHML_TEXT = 135;
+
+ /**
+ * A MathML string literal (ms in MathML).
+ */
+ const unsigned long ROLE_MATHML_STRING_LITERAL = 136;
+
+ /**
+ * A MathML glyph (mglyph in MathML).
+ */
+ const unsigned long ROLE_MATHML_GLYPH = 137;
+
+ /**
+ * A MathML row (mrow in MathML).
+ */
+ const unsigned long ROLE_MATHML_ROW = 138;
+
+ /**
+ * A MathML fraction (mfrac in MathML).
+ */
+ const unsigned long ROLE_MATHML_FRACTION = 139;
+
+ /**
+ * A MathML square root (msqrt in MathML).
+ */
+ const unsigned long ROLE_MATHML_SQUARE_ROOT = 140;
+
+ /**
+ * A MathML root (mroot in MathML).
+ */
+ const unsigned long ROLE_MATHML_ROOT = 141;
+
+ /**
+ * A MathML fenced element (mfenced in MathML).
+ */
+ const unsigned long ROLE_MATHML_FENCED = 142;
+
+ /**
+ * A MathML enclosed element (menclose in MathML).
+ */
+ const unsigned long ROLE_MATHML_ENCLOSED = 143;
+
+ /**
+ * A MathML styling element (mstyle in MathML).
+ */
+ const unsigned long ROLE_MATHML_STYLE = 144;
+
+ /**
+ * A MathML subscript (msub in MathML).
+ */
+ const unsigned long ROLE_MATHML_SUB = 145;
+
+ /**
+ * A MathML superscript (msup in MathML).
+ */
+ const unsigned long ROLE_MATHML_SUP = 146;
+
+ /**
+ * A MathML subscript and superscript (msubsup in MathML).
+ */
+ const unsigned long ROLE_MATHML_SUB_SUP = 147;
+
+ /**
+ * A MathML underscript (munder in MathML).
+ */
+ const unsigned long ROLE_MATHML_UNDER = 148;
+
+ /**
+ * A MathML overscript (mover in MathML).
+ */
+ const unsigned long ROLE_MATHML_OVER = 149;
+
+ /**
+ * A MathML underscript and overscript (munderover in MathML).
+ */
+ const unsigned long ROLE_MATHML_UNDER_OVER = 150;
+
+ /**
+ * A MathML multiple subscript and superscript element (mmultiscripts in
+ * MathML).
+ */
+ const unsigned long ROLE_MATHML_MULTISCRIPTS = 151;
+
+ /**
+ * A MathML table (mtable in MathML).
+ */
+ const unsigned long ROLE_MATHML_TABLE = 152;
+
+ /**
+ * A MathML labelled table row (mlabeledtr in MathML).
+ */
+ const unsigned long ROLE_MATHML_LABELED_ROW = 153;
+
+ /**
+ * A MathML table row (mtr in MathML).
+ */
+ const unsigned long ROLE_MATHML_TABLE_ROW = 154;
+
+ /**
+ * A MathML table entry or cell (mtd in MathML).
+ */
+ const unsigned long ROLE_MATHML_CELL = 155;
+
+ /**
+ * A MathML interactive element (maction in MathML).
+ */
+ const unsigned long ROLE_MATHML_ACTION = 156;
+
+ /**
+ * A MathML error message (merror in MathML).
+ */
+ const unsigned long ROLE_MATHML_ERROR = 157;
+
+ /**
+ * A MathML stacked (rows of numbers) element (mstack in MathML).
+ */
+ const unsigned long ROLE_MATHML_STACK = 158;
+
+ /**
+ * A MathML long division element (mlongdiv in MathML).
+ */
+ const unsigned long ROLE_MATHML_LONG_DIVISION = 159;
+
+ /**
+ * A MathML stack group (msgroup in MathML).
+ */
+ const unsigned long ROLE_MATHML_STACK_GROUP = 160;
+
+ /**
+ * A MathML stack row (msrow in MathML).
+ */
+ const unsigned long ROLE_MATHML_STACK_ROW = 161;
+
+ /**
+ * MathML carries, borrows, or crossouts for a row (mscarries in MathML).
+ */
+ const unsigned long ROLE_MATHML_STACK_CARRIES = 162;
+
+ /**
+ * A MathML carry, borrow, or crossout for a column (mscarry in MathML).
+ */
+ const unsigned long ROLE_MATHML_STACK_CARRY = 163;
+
+ /**
+ * A MathML line in a stack (msline in MathML).
+ */
+ const unsigned long ROLE_MATHML_STACK_LINE = 164;
+
+ /**
+ * A group containing radio buttons
+ */
+ const unsigned long ROLE_RADIO_GROUP = 165;
+
+ /**
+ * A text container exposing brief amount of information. See related
+ * TEXT_CONTAINER role.
+ */
+ const unsigned long ROLE_TEXT = 166;
+
+ /**
+ * A text container exposing brief amount of information. See related
+ * DETAILS role.
+ */
+ const unsigned long ROLE_DETAILS = 167;
+
+ /**
+ * A text container exposing brief amount of information. See related
+ * SUMMARY role.
+ */
+ const unsigned long ROLE_SUMMARY = 168;
+};
diff --git a/accessible/interfaces/nsIAccessibleSelectable.idl b/accessible/interfaces/nsIAccessibleSelectable.idl
new file mode 100644
index 000000000..dff2677e2
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibleSelectable.idl
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* 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 "nsISupports.idl"
+
+interface nsIAccessible;
+interface nsIArray;
+
+/**
+ * An accessibility interface for selectable widgets.
+ */
+[scriptable, builtinclass, uuid(8efb03d4-1354-4875-94cf-261336057626)]
+interface nsIAccessibleSelectable : nsISupports
+{
+ /**
+ * Return an nsIArray of selected items within the widget.
+ */
+ readonly attribute nsIArray selectedItems;
+
+ /**
+ * Return the number of currently selected items.
+ */
+ readonly attribute unsigned long selectedItemCount;
+
+ /**
+ * Return a nth selected item within the widget.
+ */
+ nsIAccessible getSelectedItemAt(in unsigned long index);
+
+ /**
+ * Return true if the given item is selected.
+ */
+ boolean isItemSelected(in unsigned long index);
+
+ /**
+ * Adds the specified item to the widget's selection.
+ */
+ void addItemToSelection(in unsigned long index);
+
+ /**
+ * Removes the specified item from the widget's selection.
+ */
+ void removeItemFromSelection(in unsigned long index);
+
+ /**
+ * Select all items.
+ *
+ * @return false if the object does not accept multiple selection,
+ * otherwise true.
+ */
+ boolean selectAll();
+
+ /**
+ * Unselect all items.
+ */
+ void unselectAll();
+};
diff --git a/accessible/interfaces/nsIAccessibleStateChangeEvent.idl b/accessible/interfaces/nsIAccessibleStateChangeEvent.idl
new file mode 100644
index 000000000..4d7505d5b
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibleStateChangeEvent.idl
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* 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 "nsIAccessibleEvent.idl"
+
+/**
+ * Fired when a state of an accessible changes.
+ */
+[scriptable, builtinclass, uuid(58b74954-1835-46ed-9ccd-c906490106f6)]
+interface nsIAccessibleStateChangeEvent : nsIAccessibleEvent
+{
+ /**
+ * Returns the state of accessible (see constants declared
+ * in nsIAccessibleStates).
+ */
+ readonly attribute unsigned long state;
+
+ /**
+ * Returns true if the state is extra state.
+ */
+ readonly attribute boolean isExtraState;
+
+ /**
+ * Returns true if the state is turned on.
+ */
+ readonly attribute boolean isEnabled;
+};
diff --git a/accessible/interfaces/nsIAccessibleStates.idl b/accessible/interfaces/nsIAccessibleStates.idl
new file mode 100644
index 000000000..ea9361e42
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibleStates.idl
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* 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 "nsISupports.idl"
+
+[scriptable, uuid(f1e0fbb7-fde4-4519-9383-2bcbee428513)]
+interface nsIAccessibleStates : nsISupports
+{
+ /**
+ * MSAA State flags - used for bitfield. More than 1 allowed.
+ */
+ const unsigned long STATE_UNAVAILABLE = 0x00000001; // Disabled, maps to opposite of Java ENABLED, Gnome/ATK SENSITIVE?
+ const unsigned long STATE_SELECTED = 0x00000002;
+ const unsigned long STATE_FOCUSED = 0x00000004;
+ const unsigned long STATE_PRESSED = 0x00000008;
+ const unsigned long STATE_CHECKED = 0x00000010;
+ const unsigned long STATE_MIXED = 0x00000020; // 3-state checkbox or toolbar button
+ const unsigned long STATE_READONLY = 0x00000040; // Maps to opposite of Java/Gnome/ATK EDITABLE state
+ const unsigned long STATE_HOTTRACKED = 0x00000080;
+ const unsigned long STATE_DEFAULT = 0x00000100;
+ const unsigned long STATE_EXPANDED = 0x00000200;
+ const unsigned long STATE_COLLAPSED = 0x00000400;
+ const unsigned long STATE_BUSY = 0x00000800;
+ const unsigned long STATE_FLOATING = 0x00001000; // Children "owned" not "contained" by parent
+ const unsigned long STATE_MARQUEED = 0x00002000;
+ const unsigned long STATE_ANIMATED = 0x00004000;
+ const unsigned long STATE_INVISIBLE = 0x00008000; // Programatically hidden
+ const unsigned long STATE_OFFSCREEN = 0x00010000; // Scrolled off
+ const unsigned long STATE_SIZEABLE = 0x00020000;
+ const unsigned long STATE_MOVEABLE = 0x00040000;
+ const unsigned long STATE_SELFVOICING = 0x00080000;
+ const unsigned long STATE_FOCUSABLE = 0x00100000;
+ const unsigned long STATE_SELECTABLE = 0x00200000;
+ const unsigned long STATE_LINKED = 0x00400000;
+ const unsigned long STATE_TRAVERSED = 0x00800000;
+ const unsigned long STATE_MULTISELECTABLE = 0x01000000; // Supports multiple selection
+ const unsigned long STATE_EXTSELECTABLE = 0x02000000; // Supports extended selection
+ const unsigned long STATE_ALERT_LOW = 0x04000000; // This information is of low priority
+ const unsigned long STATE_ALERT_MEDIUM = 0x08000000; // This information is of medium priority
+ const unsigned long STATE_ALERT_HIGH = 0x10000000; // This information is of high priority
+ const unsigned long STATE_PROTECTED = 0x20000000; // Maps to Gnome's *Role* ATK_ROLE_PASSWD_TEXT, nothing for Java?
+ const unsigned long STATE_HASPOPUP = 0x40000000; // New in MSAA 2.0
+
+ // Mapping important states that we don't have to unused alert states on MSAA
+ // as per discussions with AT vendors. On ATK there will be legitimate states for
+ // STATE_REQUIRED AND STATE_INVALID
+ const unsigned long STATE_REQUIRED = STATE_ALERT_LOW;
+ const unsigned long STATE_IMPORTANT = STATE_ALERT_MEDIUM; // Not currently used
+ const unsigned long STATE_INVALID = STATE_ALERT_HIGH;
+ const unsigned long STATE_CHECKABLE = STATE_MARQUEED;
+
+/**
+ * Extended state flags (for now non-MSAA, for Java and Gnome/ATK support)
+ * "Extended state flags" has separate value space from "MSAA State flags".
+ */
+ const unsigned long EXT_STATE_SUPPORTS_AUTOCOMPLETION = 0x00000001; // For editable areas that have any kind of autocompletion
+ const unsigned long EXT_STATE_DEFUNCT = 0x00000002; // Object no longer exists
+ const unsigned long EXT_STATE_SELECTABLE_TEXT = 0x00000004; // For text which is selectable, object must implement nsIAccessibleText
+ const unsigned long EXT_STATE_EDITABLE = 0x00000008; // Implements nsIAccessibleEditableText
+ const unsigned long EXT_STATE_ACTIVE = 0x00000010; // This window is currently the active window
+ const unsigned long EXT_STATE_MODAL = 0x00000020; // Must do something with control before leaving it
+ const unsigned long EXT_STATE_MULTI_LINE = 0x00000040; // Edit control that can take multiple lines
+ const unsigned long EXT_STATE_HORIZONTAL = 0x00000080; // Uses horizontal layout
+ const unsigned long EXT_STATE_OPAQUE = 0x00000100; // Indicates this object paints every pixel within its rectangular region.
+ const unsigned long EXT_STATE_SINGLE_LINE = 0x00000200; // This text object can only contain 1 line of text
+ const unsigned long EXT_STATE_TRANSIENT = 0x00000400; //
+ const unsigned long EXT_STATE_VERTICAL = 0x00000800; // Especially used for sliders and scrollbars
+ const unsigned long EXT_STATE_STALE = 0x00001000; // Object not dead, but not up-to-date either
+ const unsigned long EXT_STATE_ENABLED = 0x00002000; // A widget that is not unavailable
+ const unsigned long EXT_STATE_SENSITIVE = 0x00004000; // Same as ENABLED for now
+ const unsigned long EXT_STATE_EXPANDABLE = 0x00008000; // If COLLAPSED or EXPANDED
+ const unsigned long EXT_STATE_PINNED = 0x00010000; // Indicates object is pinned.
+};
+
diff --git a/accessible/interfaces/nsIAccessibleTable.idl b/accessible/interfaces/nsIAccessibleTable.idl
new file mode 100644
index 000000000..8d1469d63
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibleTable.idl
@@ -0,0 +1,268 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 "nsISupports.idl"
+
+interface nsIAccessible;
+interface nsIArray;
+
+[scriptable, builtinclass, uuid(cb0bf7b9-117e-40e2-9e46-189c3d43ce4a)]
+interface nsIAccessibleTable : nsISupports
+{
+ /**
+ * Return the caption accessible for the table. For example, html:caption
+ * element of html:table element.
+ */
+ readonly attribute nsIAccessible caption;
+
+ /**
+ * Return summary description for the table. For example, @summary attribute
+ * on html:table element.
+ */
+ readonly attribute AString summary;
+
+ /**
+ * Return columns count in the table.
+ */
+ readonly attribute long columnCount;
+
+ /**
+ * Return rows count in the table.
+ */
+ readonly attribute long rowCount;
+
+ /**
+ * Return the accessible object at the specified row and column in the table.
+ * If both row and column index are valid then the corresponding accessible
+ * object is returned that represents the requested cell regardless of whether
+ * the cell is currently visible (on the screen).
+ *
+ * @param rowIndex [in] the row index to retrieve the cell at
+ * @param columnIndex [in] the column index to retrieve the cell at
+ */
+ nsIAccessible getCellAt(in long rowIndex, in long columnIndex);
+
+ /**
+ * Translate the given row and column indices into the corresponding cell
+ * index.
+ *
+ * @param rowIndex [in] the row index to return cell index at
+ * @param columnIndex [in] the column index to return cell index at
+ */
+ long getCellIndexAt(in long rowIndex, in long columnIndex);
+
+ /**
+ * Translate the given cell index into the corresponding column index.
+ *
+ * @param cellIndex [in] index of the table cell to return column index for
+ */
+ long getColumnIndexAt(in long cellIndex);
+
+ /**
+ * Translate the given cell index into the corresponding row index.
+ *
+ * @param cellIndex [in] index of the table cell to return row index for
+ */
+ long getRowIndexAt(in long cellIndex);
+
+ /**
+ * Translate the given cell index into the corresponding row and column
+ * indices.
+ *
+ * @param cellIndex [in] cell index to return row and column indices for
+ * @param rowIndex [out] row index at the given cell index
+ * @param columnIndex [out] column index at the given cell index
+ */
+ void getRowAndColumnIndicesAt(in long cellIndex,
+ out long rowIndex, out long columnIndex);
+
+ /**
+ * Return the number of columns occupied by the accessible cell at
+ * the specified row and column in the table. The result differs from 1 if
+ * the specified cell spans multiple columns.
+ *
+ * @param row [in] row index of the cell to return the column extent for
+ * @param column [in] column index of the cell to return the column extent
+ * for
+ */
+ long getColumnExtentAt(in long row, in long column);
+
+ /**
+ * Return the number of rows occupied by the accessible cell at the specified
+ * row and column in the table. The result differs from 1 if the specified
+ * cell spans multiple rows.
+ *
+ * @param row [in] row index of the cell to return the column extent for
+ * @param column [in] column index of the cell to return the column extent
+ * for
+ */
+ long getRowExtentAt(in long row, in long column);
+
+ /**
+ * Return the description text of the specified column in the table.
+ *
+ * @param columnIndex [in] the column index to retrieve description for
+ */
+ AString getColumnDescription(in long columnIndex);
+
+ /**
+ * Return the description text of the specified row in the table.
+ *
+ * @param rowIndex [in] the row index to retrieve description for
+ */
+ AString getRowDescription(in long rowIndex);
+
+ /**
+ * Return a boolean value indicating whether the specified column is
+ * selected, i.e. all cells within the column are selected.
+ *
+ * @param columnIndex [in] the column index to determine if it's selected
+ */
+ boolean isColumnSelected(in long columnIndex);
+
+ /**
+ * Return a boolean value indicating whether the specified row is selected,
+ * i.e. all cells within the row are selected.
+ *
+ * @param rowIndex [in] the row index to determine whether it's selected
+ */
+ boolean isRowSelected(in long rowIndex);
+
+ /**
+ * Return a boolean value indicating whether the specified cell is selected.
+ *
+ * @param rowIndex [in] the row index of the cell
+ * @param columnIndex [in] the column index of the cell
+ */
+ boolean isCellSelected(in long rowIndex, in long columnIndex);
+
+ /**
+ * Return the total number of selected cells.
+ */
+ readonly attribute unsigned long selectedCellCount;
+
+ /**
+ * Return the total number of selected columns.
+ */
+ readonly attribute unsigned long selectedColumnCount;
+
+ /**
+ * Return the total number of selected rows.
+ */
+ readonly attribute unsigned long selectedRowCount;
+
+ /**
+ * Return an array of selected cells.
+ */
+ readonly attribute nsIArray selectedCells;
+
+ /**
+ * Return an array of cell indices currently selected.
+ *
+ * @param cellsArraySize [in] length of array
+ * @param cellsArray [in] array of indexes of selected cells
+ */
+ void getSelectedCellIndices(out unsigned long cellsArraySize,
+ [retval, array, size_is(cellsArraySize)] out long cellsArray);
+
+ /**
+ * Return an array of column indices currently selected.
+ *
+ * @param columnsArraySize [in] length of array
+ * @param columnsArray [in] array of indices of selected columns
+ */
+ void getSelectedColumnIndices(out unsigned long columnsArraySize,
+ [retval, array, size_is(columnsArraySize)] out long columnsArray);
+
+ /**
+ * Return an array of row indices currently selected.
+ *
+ * @param rowsArraySize [in] Length of array
+ * @param rowsArray [in] array of indices of selected rows
+ */
+ void getSelectedRowIndices(out unsigned long rowsArraySize,
+ [retval, array, size_is(rowsArraySize)] out long rowsArray);
+
+ /**
+ * Select a row and unselects all previously selected rows.
+ *
+ * @param rowIndex [in] the row index to select
+ */
+ void selectRow(in long rowIndex);
+
+ /**
+ * Select a column and unselects all previously selected columns.
+ *
+ * @param columnIndex [in] the column index to select
+ */
+ void selectColumn(in long columnIndex);
+
+ /**
+ * Unselect the given row, leaving other selected rows selected (if any).
+ *
+ * @param rowIndex [in] the row index to select
+ */
+ void unselectRow(in long rowIndex);
+
+ /**
+ * Unselect the given column, leaving other selected columns selected (if any).
+ *
+ * @param columnIndex [in] the column index to select
+ */
+ void unselectColumn(in long columnIndex);
+
+ /**
+ * Use heuristics to determine if table is most likely used for layout.
+ */
+ boolean isProbablyForLayout();
+};
+
+
+[scriptable, builtinclass, uuid(654e296d-fae6-452b-987d-746b20b9514b)]
+interface nsIAccessibleTableCell : nsISupports
+{
+ /**
+ * Return host table accessible.
+ */
+ readonly attribute nsIAccessibleTable table;
+
+ /**
+ * Return column index of this cell.
+ */
+ readonly attribute long columnIndex;
+
+ /**
+ * Return row index of this cell.
+ */
+ readonly attribute long rowIndex;
+
+ /**
+ * Return the number of columns occupied by this cell. The result differs
+ * from 1 if the specified cell spans multiple columns.
+ */
+ readonly attribute long columnExtent;
+
+ /**
+ * Return the number of rows occupied by this accessible cell. The result
+ * differs from 1 if the specified cell spans multiple rows.
+ */
+ readonly attribute long rowExtent;
+
+ /**
+ * Return an array of column header cells for this cell.
+ */
+ readonly attribute nsIArray columnHeaderCells;
+
+ /**
+ * Return an array of row header cells for this cell.
+ */
+ readonly attribute nsIArray rowHeaderCells;
+
+ /**
+ * Return a boolean value indicating whether this cell is selected.
+ */
+ boolean isSelected();
+};
diff --git a/accessible/interfaces/nsIAccessibleTableChangeEvent.idl b/accessible/interfaces/nsIAccessibleTableChangeEvent.idl
new file mode 100644
index 000000000..f8804e6da
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibleTableChangeEvent.idl
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* 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 "nsIAccessibleEvent.idl"
+
+[scriptable, builtinclass, uuid(9fb3a8a4-d254-43d3-80a5-20e171d52b21)]
+interface nsIAccessibleTableChangeEvent: nsIAccessibleEvent
+{
+ /**
+ * Return the row or column index.
+ */
+ readonly attribute long rowOrColIndex;
+
+ /**
+ * Return the number of rows or cols
+ */
+ readonly attribute long RowsOrColsCount;
+};
diff --git a/accessible/interfaces/nsIAccessibleText.idl b/accessible/interfaces/nsIAccessibleText.idl
new file mode 100644
index 000000000..7612fbfe2
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibleText.idl
@@ -0,0 +1,237 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 "nsISupports.idl"
+
+typedef long AccessibleTextBoundary;
+
+interface nsIAccessible;
+interface nsIArray;
+interface nsIPersistentProperties;
+interface nsIAccessibleTextRange;
+
+[scriptable, builtinclass, uuid(93ad2ca1-f12b-4ab9-a793-95d9fa9d1774)]
+interface nsIAccessibleText : nsISupports
+{
+ // In parameters for character offsets:
+ // -1 will be treated as the equal to the end of the text
+ // -2 will be treated as the caret position
+ const int32_t TEXT_OFFSET_END_OF_TEXT = -1;
+ const int32_t TEXT_OFFSET_CARET = -2;
+
+ const AccessibleTextBoundary BOUNDARY_CHAR = 0;
+ const AccessibleTextBoundary BOUNDARY_WORD_START = 1;
+ const AccessibleTextBoundary BOUNDARY_WORD_END = 2;
+ const AccessibleTextBoundary BOUNDARY_SENTENCE_START = 3; // don't use, deprecated
+ const AccessibleTextBoundary BOUNDARY_SENTENCE_END = 4; // don't use, deprecated
+ const AccessibleTextBoundary BOUNDARY_LINE_START = 5;
+ const AccessibleTextBoundary BOUNDARY_LINE_END = 6;
+
+ /**
+ * The current current caret offset.
+ * If set < 0 then caret will be placed at the end of the text
+ */
+ attribute long caretOffset;
+
+ readonly attribute long characterCount;
+ readonly attribute long selectionCount;
+
+ /**
+ * String methods may need to return multibyte-encoded strings,
+ * since some locales can't be encoded using 16-bit chars.
+ * So the methods below might return UTF-16 strings, or they could
+ * return "string" values which are UTF-8.
+ */
+ AString getText (in long startOffset, in long endOffset);
+
+ AString getTextAfterOffset (in long offset,
+ in AccessibleTextBoundary boundaryType,
+ out long startOffset,
+ out long endOffset);
+
+ AString getTextAtOffset (in long offset,
+ in AccessibleTextBoundary boundaryType,
+ out long startOffset,
+ out long endOffset);
+
+ AString getTextBeforeOffset (in long offset,
+ in AccessibleTextBoundary boundaryType,
+ out long startOffset,
+ out long endOffset);
+
+ /**
+ * It would be better to return an unsigned long here,
+ * to allow unicode chars > 16 bits
+ */
+ wchar getCharacterAtOffset (in long offset);
+
+ /**
+ * Get the accessible start/end offsets around the given offset,
+ * return the text attributes for this range of text.
+ *
+ * @param includeDefAttrs [in] points whether text attributes applied to
+ * the entire accessible should be included or not.
+ * @param offset [in] text offset
+ * @param rangeStartOffset [out] start offset of the range of text
+ * @param rangeEndOffset [out] end offset of the range of text
+ */
+ nsIPersistentProperties getTextAttributes(in boolean includeDefAttrs,
+ in long offset,
+ out long rangeStartOffset,
+ out long rangeEndOffset);
+
+ /**
+ * Return the text attributes that apply to the entire accessible.
+ */
+ readonly attribute nsIPersistentProperties defaultTextAttributes;
+
+ /**
+ * Returns the bounding box of the specified position.
+ *
+ * The virtual character after the last character of the represented text,
+ * i.e. the one at position length is a special case. It represents the
+ * current input position and will therefore typically be queried by AT more
+ * often than other positions. Because it does not represent an existing
+ * character its bounding box is defined in relation to preceding characters.
+ * It should be roughly equivalent to the bounding box of some character when
+ * inserted at the end of the text. Its height typically being the maximal
+ * height of all the characters in the text or the height of the preceding
+ * character, its width being at least one pixel so that the bounding box is
+ * not degenerate.
+ *
+ * @param offset - Index of the character for which to return its bounding
+ * box. The valid range is 0..length.
+ * @param x - X coordinate of the bounding box of the referenced character.
+ * @param y - Y coordinate of the bounding box of the referenced character.
+ * @param width - Width of the bounding box of the referenced character.
+ * @param height - Height of the bounding box of the referenced character.
+ * @param coordType - Specifies if the coordinates are relative to the screen
+ * or to the parent window (see constants declared in
+ * nsIAccessibleCoordinateType).
+ */
+ void getCharacterExtents (in long offset,
+ out long x,
+ out long y,
+ out long width,
+ out long height,
+ in unsigned long coordType);
+
+ void getRangeExtents (in long startOffset,
+ in long endOffset,
+ out long x,
+ out long y,
+ out long width,
+ out long height,
+ in unsigned long coordType);
+
+ /**
+ * Get the text offset at the given point, or return -1
+ * if no character exists at that point
+ *
+ * @param x - The position's x value for which to look up the index of the
+ * character that is rendered on to the display at that point.
+ * @param y - The position's y value for which to look up the index of the
+ * character that is rendered on to the display at that point.
+ * @param coordType - Screen coordinates or window coordinates (see constants
+ * declared in nsIAccessibleCoordinateType).
+ * @return offset - Index of the character under the given point or -1 if
+ * the point is invalid or there is no character under
+ * the point.
+ */
+ long getOffsetAtPoint (in long x, in long y,
+ in unsigned long coordType);
+
+ void getSelectionBounds (in long selectionNum,
+ out long startOffset,
+ out long endOffset);
+
+ /**
+ * Set the bounds for the given selection range
+ */
+ void setSelectionBounds (in long selectionNum,
+ in long startOffset,
+ in long endOffset);
+
+ void addSelection (in long startOffset, in long endOffset);
+
+ void removeSelection (in long selectionNum);
+
+
+ /**
+ * Makes a specific part of string visible on screen.
+ *
+ * @param startIndex 0-based character offset
+ * @param endIndex 0-based character offset - the offset of the
+ * character just past the last character of the
+ * string
+ * @param scrollType defines how to scroll (see nsIAccessibleScrollType for
+ * available constants)
+ */
+ void scrollSubstringTo(in long startIndex, in long endIndex,
+ in unsigned long scrollType);
+
+ /**
+ * Moves the top left of a substring to a specified location.
+ *
+ * @param startIndex 0-based character offset
+ * @param endIndex 0-based character offset - the offset of the
+ * character just past the last character of
+ * the string
+ * @param coordinateType specifies the coordinates origin (for available
+ * constants refer to nsIAccessibleCoordinateType)
+ * @param x defines the x coordinate
+ * @param y defines the y coordinate
+ */
+ void scrollSubstringToPoint(in long startIndex, in long endIndex,
+ in unsigned long coordinateType,
+ in long x, in long y);
+
+ /**
+ * Return a range that encloses this text control or otherwise the document
+ * this text accessible belongs to.
+ */
+ readonly attribute nsIAccessibleTextRange enclosingRange;
+
+ /**
+ * Return an array of disjoint ranges for selected text within the text control
+ * or otherwise the document this accessible belongs to.
+ */
+ readonly attribute nsIArray selectionRanges;
+
+ /**
+ * Return an array of disjoint ranges of visible text within the text control
+ * or otherwise the document this accessible belongs to.
+ */
+ readonly attribute nsIArray visibleRanges;
+
+ /**
+ * Return a range containing the given accessible.
+ */
+ nsIAccessibleTextRange getRangeByChild(in nsIAccessible child);
+
+ /**
+ * Return a range containing an accessible at the given point.
+ */
+ nsIAccessibleTextRange getRangeAtPoint(in long x, in long y);
+};
+
+/*
+ Assumptions:
+
+ Using wstring (UCS2) instead of string encoded in UTF-8.
+ Multibyte encodings (or at least potentially multi-byte
+ encodings) would be preferred for the reasons cited above.
+
+ The following methods will throw an exception on failure
+ (since not every text component will allow every operation):
+ setSelectionBounds, addSelection, removeSelection, setCaretOffset.
+
+ we assume that all text components support the idea of
+ a caret offset, whether visible or "virtual". If this
+ isn't the case, caretOffset can be made readonly and
+ a setCaretOffset method provided which throws an exception
+ on failure (as with *selection methods above).
+*/
diff --git a/accessible/interfaces/nsIAccessibleTextChangeEvent.idl b/accessible/interfaces/nsIAccessibleTextChangeEvent.idl
new file mode 100644
index 000000000..e171ab0f4
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibleTextChangeEvent.idl
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* 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 "nsIAccessibleEvent.idl"
+
+/**
+ * Fired when an accessible's text changes.
+ */
+[scriptable, builtinclass, uuid(1fcc0dfa-93e6-48f4-bbd4-f80eb1d9f2e6)]
+interface nsIAccessibleTextChangeEvent : nsIAccessibleEvent
+{
+ /**
+ * Returns offset of changed text in accessible.
+ */
+ readonly attribute long start;
+
+ /**
+ * Returns length of changed text.
+ */
+ readonly attribute unsigned long length;
+
+ /**
+ * Returns true if text was inserted, otherwise false.
+ */
+ readonly attribute boolean isInserted;
+
+ /**
+ * The inserted or removed text
+ */
+ readonly attribute DOMString modifiedText;
+};
diff --git a/accessible/interfaces/nsIAccessibleTextRange.idl b/accessible/interfaces/nsIAccessibleTextRange.idl
new file mode 100644
index 000000000..1c351c734
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibleTextRange.idl
@@ -0,0 +1,159 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* 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 "nsISupports.idl"
+
+interface nsIAccessible;
+interface nsIAccessibleText;
+interface nsIArray;
+interface nsIVariant;
+
+/**
+ * A range representing a piece of text in the document.
+ */
+[scriptable, builtinclass, uuid(c4515623-55f9-4543-a3d5-c1e9afa588f4)]
+interface nsIAccessibleTextRange : nsISupports
+{
+ readonly attribute nsIAccessibleText startContainer;
+ readonly attribute long startOffset;
+ readonly attribute nsIAccessibleText endContainer;
+ readonly attribute long endOffset;
+
+ /**
+ * Return an accessible containing the whole range
+ */
+ readonly attribute nsIAccessible container;
+
+ /**
+ * Return embedded children within the range.
+ */
+ readonly attribute nsIArray embeddedChildren;
+
+ /**
+ * Return true if this range has the same end points of the given range.
+ */
+ boolean compare(in nsIAccessibleTextRange aOtherRange);
+
+ /**
+ * The two endpoints of the range (starting and ending).
+ */
+ const unsigned long EndPoint_Start = 1;
+ const unsigned long EndPoint_End = 2;
+
+ /**
+ * Compare this and given ranges end points.
+ *
+ * @return -1/0/1 if this range end point is before/equal/after the given
+ * range end point.
+ */
+ long compareEndPoints(in unsigned long aEndPoint,
+ in nsIAccessibleTextRange aOtherRange,
+ in unsigned long aOtherRangeEndPoint);
+
+ /**
+ * Return text within the range.
+ */
+ readonly attribute AString text;
+
+ /**
+ * Return list of rects of the range.
+ */
+ readonly attribute nsIArray bounds;
+
+ const unsigned long FormatUnit = 0;
+ const unsigned long WordUnit = 1;
+ const unsigned long LineUnit = 2;
+ const unsigned long ParagraphUnit = 3;
+ const unsigned long PageUnit = 4;
+ const unsigned long DocumentUnit = 5;
+
+ /**
+ * Move the boundary(ies) by the given number of the unit.
+ */
+ void move(in unsigned long aUnit, in long aCount);
+ void moveStart(in unsigned long aUnit, in long aCount);
+ void moveEnd(in unsigned long aUnit, in long aCount);
+
+ /**
+ * Normalize the range to the closest unit of the given type.
+ */
+ void normalize(in unsigned long aUnit);
+
+ /**
+ * Crops the range by the given accessible element.
+ */
+ boolean crop(in nsIAccessible aContainer);
+
+ /**
+ * Return range enclosing the found text.
+ */
+ nsIAccessibleTextRange findText(in AString aText, in boolean aIsBackward,
+ in boolean aIsIgnoreCase);
+
+ /**
+ * Text attributes. Used in conjunction with findAttrs().
+ */
+ const unsigned long AnimationStyleAttr = 0;
+ const unsigned long AnnotationObjectsAttr = 1;
+ const unsigned long AnnotationTypesAttr = 2;
+ const unsigned long BackgroundColorAttr = 3;
+ const unsigned long BulletStyleAttr = 4;
+ const unsigned long CapStyleAttr = 5;
+ const unsigned long CaretBidiModeAttr = 6;
+ const unsigned long CaretPositionAttr = 7;
+ const unsigned long CultureAttr = 8;
+ const unsigned long FontNameAttr = 9;
+ const unsigned long FontSizeAttr = 10;
+ const unsigned long FontWeightAttr = 11;
+ const unsigned long ForegroundColorAttr = 12;
+ const unsigned long HorizontalTextAlignmentAttr = 13;
+ const unsigned long IndentationFirstLineAttr = 14;
+ const unsigned long IndentationLeadingAttr = 15;
+ const unsigned long IndentationTrailingAttr = 16;
+ const unsigned long IsActiveAttr = 17;
+ const unsigned long IsHiddenAttr = 18;
+ const unsigned long IsItalicAttr = 19;
+ const unsigned long IsReadOnlyAttr = 20;
+ const unsigned long IsSubscriptAttr = 21;
+ const unsigned long IsSuperscriptAttr = 22;
+ const unsigned long LinkAttr = 23;
+ const unsigned long MarginBottomAttr = 24;
+ const unsigned long MarginLeadingAttr = 25;
+ const unsigned long MarginTopAttr = 26;
+ const unsigned long MarginTrailingAttr = 27;
+ const unsigned long OutlineStylesAttr = 28;
+ const unsigned long OverlineColorAttr = 29;
+ const unsigned long OverlineStyleAttr = 30;
+ const unsigned long SelectionActiveEndAttr = 31;
+ const unsigned long StrikethroughColorAttr = 32;
+ const unsigned long StrikethroughStyleAttr = 33;
+ const unsigned long StyleIdAttr = 34;
+ const unsigned long StyleNameAttr = 35;
+ const unsigned long TabsAttr = 36;
+ const unsigned long TextFlowDirectionsAttr = 37;
+ const unsigned long UnderlineColorAttr = 38;
+ const unsigned long UnderlineStyleAttr = 39;
+
+ /**
+ * Return range enslosing the text having requested attribute.
+ */
+ nsIAccessibleTextRange findAttr(in unsigned long aAttr, in nsIVariant aValue,
+ in boolean aIsBackward);
+
+ /**
+ * Add/remove the text range from selection.
+ */
+ void addToSelection();
+ void removeFromSelection();
+ void select();
+
+ const unsigned long AlignToTop = 0;
+ const unsigned long AlignToBottom = 1;
+
+ /**
+ * Scroll the range into view.
+ */
+ void scrollIntoView(in unsigned long aHow);
+};
diff --git a/accessible/interfaces/nsIAccessibleTypes.idl b/accessible/interfaces/nsIAccessibleTypes.idl
new file mode 100644
index 000000000..5f0ac4274
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibleTypes.idl
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* 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 "nsISupports.idl"
+
+/**
+ * These constants control the scrolling of an object or substring into a
+ * window. Note, keep them synchronized with IA2ScrollType.
+ */
+[scriptable, builtinclass, uuid(05cd38b1-94b3-4cdf-8371-3935a9611405)]
+interface nsIAccessibleScrollType : nsISupports
+{
+ /**
+ * Scroll the top left of the object or substring to the top left of the
+ * window (or as close as possible).
+ */
+ const unsigned long SCROLL_TYPE_TOP_LEFT =0x00;
+
+ /**
+ * Scroll the bottom right of the object or substring to the bottom right of
+ * the window (or as close as possible).
+ */
+ const unsigned long SCROLL_TYPE_BOTTOM_RIGHT = 0x01;
+
+ /**
+ * Scroll the top edge of the object or substring to the top edge of the
+ * window (or as close as possible).
+ */
+ const unsigned long SCROLL_TYPE_TOP_EDGE = 0x02;
+
+ /**
+ * Scroll the bottom edge of the object or substring to the bottom edge of
+ * the window (or as close as possible).
+ */
+ const unsigned long SCROLL_TYPE_BOTTOM_EDGE = 0x03;
+
+ /**
+ * Scroll the left edge of the object or substring to the left edge of the
+ * window (or as close as possible).
+ */
+ const unsigned long SCROLL_TYPE_LEFT_EDGE =0x04;
+
+ /**
+ * Scroll the right edge of the object or substring to the right edge of the
+ * window (or as close as possible).
+ */
+ const unsigned long SCROLL_TYPE_RIGHT_EDGE = 0x05;
+
+ /**
+ * Scroll an object the minimum amount necessary in order for the entire
+ * frame to be visible (if possible).
+ */
+ const unsigned long SCROLL_TYPE_ANYWHERE = 0x06;
+};
+
+
+/**
+ * These constants define which coordinate system a point is located in.
+ */
+[scriptable, builtinclass, uuid(c9fbdf10-619e-436f-bf4b-8566686f1577)]
+interface nsIAccessibleCoordinateType : nsISupports
+{
+ /**
+ * The coordinates are relative to the screen.
+ */
+ const unsigned long COORDTYPE_SCREEN_RELATIVE = 0x00;
+
+ /**
+ * The coordinates are relative to the window.
+ */
+ const unsigned long COORDTYPE_WINDOW_RELATIVE = 0x01;
+
+ /**
+ * The coordinates are relative to the upper left corner of the bounding box
+ * of the immediate parent.
+ */
+ const unsigned long COORDTYPE_PARENT_RELATIVE = 0x02;
+};
+
diff --git a/accessible/interfaces/nsIAccessibleValue.idl b/accessible/interfaces/nsIAccessibleValue.idl
new file mode 100644
index 000000000..886fcb8fe
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibleValue.idl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 "nsISupports.idl"
+
+[scriptable, builtinclass, uuid(42a1e1dc-58cf-419d-bff0-ed3314c70016)]
+interface nsIAccessibleValue : nsISupports
+{
+ readonly attribute double maximumValue;
+ readonly attribute double minimumValue;
+ attribute double currentValue;
+ readonly attribute double minimumIncrement;
+};
+
+/*
+ Assumptions:
+
+ The attribute currentValue will throw an exception
+ if it cannot be set i.e. if the value is not a
+ member of the interval.
+ This may not be the 'desired' behaviour given gObject
+ equivalent. Thus it could be changed to be:
+
+ readonly attribute double currentValue;
+ boolean setCurrentValue (double long value);
+
+ GValue can represent many basic types.
+ Since this interface is designed to represent
+ an interval and a member of double should
+ cover the cases of char int and float.
+
+*/
diff --git a/accessible/interfaces/nsIAccessibleVirtualCursorChangeEvent.idl b/accessible/interfaces/nsIAccessibleVirtualCursorChangeEvent.idl
new file mode 100644
index 000000000..98dca0bc9
--- /dev/null
+++ b/accessible/interfaces/nsIAccessibleVirtualCursorChangeEvent.idl
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+/* 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 "nsIAccessibleEvent.idl"
+
+/*
+ * An interface for virtual cursor changed events.
+ * Passes previous cursor position and text offsets.
+ */
+[scriptable, builtinclass, uuid(a58693b1-009e-4cc9-ae93-9c7d8f85cfdf)]
+interface nsIAccessibleVirtualCursorChangeEvent : nsIAccessibleEvent
+{
+ /**
+ * Previous object pointed at by virtual cursor. null if none.
+ */
+ readonly attribute nsIAccessible oldAccessible;
+
+ /**
+ * Previous start offset of pivot. -1 if none.
+ */
+ readonly attribute long oldStartOffset;
+
+ /**
+ * Previous end offset of pivot. -1 if none.
+ */
+ readonly attribute long oldEndOffset;
+
+ /**
+ * Reason for virtual cursor move.
+ */
+ readonly attribute short reason;
+};
diff --git a/accessible/interfaces/nsIXBLAccessible.idl b/accessible/interfaces/nsIXBLAccessible.idl
new file mode 100644
index 000000000..054ee62bd
--- /dev/null
+++ b/accessible/interfaces/nsIXBLAccessible.idl
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 "nsISupports.idl"
+
+/**
+ * XBL controls can implement this interface to provide own implementation of
+ * accessible properties.
+ */
+[scriptable, builtinclass, uuid(3716eb86-166b-445b-a94a-9b522fee96e6)]
+interface nsIXBLAccessible : nsISupports
+{
+ /**
+ * Return accessible name.
+ */
+ readonly attribute DOMString accessibleName;
+};
diff --git a/accessible/ipc/DocAccessibleChildBase.cpp b/accessible/ipc/DocAccessibleChildBase.cpp
new file mode 100644
index 000000000..6a0ad9b7d
--- /dev/null
+++ b/accessible/ipc/DocAccessibleChildBase.cpp
@@ -0,0 +1,97 @@
+/* -*- 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 "mozilla/a11y/DocAccessibleChildBase.h"
+#include "mozilla/a11y/ProxyAccessible.h"
+
+#include "Accessible-inl.h"
+
+namespace mozilla {
+namespace a11y {
+
+/* static */ uint32_t
+DocAccessibleChildBase::InterfacesFor(Accessible* aAcc)
+{
+ uint32_t interfaces = 0;
+ if (aAcc->IsHyperText() && aAcc->AsHyperText()->IsTextRole())
+ interfaces |= Interfaces::HYPERTEXT;
+
+ if (aAcc->IsLink())
+ interfaces |= Interfaces::HYPERLINK;
+
+ if (aAcc->HasNumericValue())
+ interfaces |= Interfaces::VALUE;
+
+ if (aAcc->IsImage())
+ interfaces |= Interfaces::IMAGE;
+
+ if (aAcc->IsTable()) {
+ interfaces |= Interfaces::TABLE;
+ }
+
+ if (aAcc->IsTableCell())
+ interfaces |= Interfaces::TABLECELL;
+
+ if (aAcc->IsDoc())
+ interfaces |= Interfaces::DOCUMENT;
+
+ if (aAcc->IsSelect()) {
+ interfaces |= Interfaces::SELECTION;
+ }
+
+ if (aAcc->ActionCount()) {
+ interfaces |= Interfaces::ACTION;
+ }
+
+ return interfaces;
+}
+
+/* static */ void
+DocAccessibleChildBase::SerializeTree(Accessible* aRoot,
+ nsTArray<AccessibleData>& aTree)
+{
+ uint64_t id = reinterpret_cast<uint64_t>(aRoot->UniqueID());
+#if defined(XP_WIN)
+ int32_t msaaId = AccessibleWrap::GetChildIDFor(aRoot);
+#endif
+ uint32_t role = aRoot->Role();
+ uint32_t childCount = aRoot->ChildCount();
+ uint32_t interfaces = InterfacesFor(aRoot);
+
+ // OuterDocAccessibles are special because we don't want to serialize the
+ // child doc here, we'll call PDocAccessibleConstructor in
+ // NotificationController.
+ MOZ_ASSERT(!aRoot->IsDoc(), "documents shouldn't be serialized");
+ if (aRoot->IsOuterDoc()) {
+ childCount = 0;
+ }
+
+#if defined(XP_WIN)
+ aTree.AppendElement(AccessibleData(id, msaaId, role, childCount, interfaces));
+#else
+ aTree.AppendElement(AccessibleData(id, role, childCount, interfaces));
+#endif
+
+ for (uint32_t i = 0; i < childCount; i++) {
+ SerializeTree(aRoot->GetChildAt(i), aTree);
+ }
+}
+
+void
+DocAccessibleChildBase::ShowEvent(AccShowEvent* aShowEvent)
+{
+ Accessible* parent = aShowEvent->Parent();
+ uint64_t parentID = parent->IsDoc() ? 0 : reinterpret_cast<uint64_t>(parent->UniqueID());
+ uint32_t idxInParent = aShowEvent->GetAccessible()->IndexInParent();
+ nsTArray<AccessibleData> shownTree;
+ ShowEventData data(parentID, idxInParent, shownTree);
+ SerializeTree(aShowEvent->GetAccessible(), data.NewTree());
+ MaybeSendShowEvent(data, aShowEvent->IsFromUserInput());
+}
+
+} // namespace a11y
+} // namespace mozilla
+
diff --git a/accessible/ipc/DocAccessibleChildBase.h b/accessible/ipc/DocAccessibleChildBase.h
new file mode 100644
index 000000000..b8a8bfde1
--- /dev/null
+++ b/accessible/ipc/DocAccessibleChildBase.h
@@ -0,0 +1,81 @@
+/* -*- 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_DocAccessibleChildBase_h
+#define mozilla_a11y_DocAccessibleChildBase_h
+
+#include "mozilla/a11y/DocAccessible.h"
+#include "mozilla/a11y/PDocAccessibleChild.h"
+#include "mozilla/Unused.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+class AccShowEvent;
+
+class DocAccessibleChildBase : public PDocAccessibleChild
+{
+public:
+ explicit DocAccessibleChildBase(DocAccessible* aDoc)
+ : mDoc(aDoc)
+ {
+ MOZ_COUNT_CTOR(DocAccessibleChildBase);
+ }
+
+ ~DocAccessibleChildBase()
+ {
+ // Shutdown() should have been called, but maybe it isn't if the process is
+ // killed?
+ MOZ_ASSERT(!mDoc);
+ if (mDoc) {
+ mDoc->SetIPCDoc(nullptr);
+ }
+
+ MOZ_COUNT_DTOR(DocAccessibleChildBase);
+ }
+
+ virtual void Shutdown()
+ {
+ DetachDocument();
+ SendShutdown();
+ }
+
+ void ShowEvent(AccShowEvent* aShowEvent);
+
+ virtual void ActorDestroy(ActorDestroyReason) override
+ {
+ if (!mDoc) {
+ return;
+ }
+
+ mDoc->SetIPCDoc(nullptr);
+ mDoc = nullptr;
+ }
+
+protected:
+ static uint32_t InterfacesFor(Accessible* aAcc);
+ static void SerializeTree(Accessible* aRoot, nsTArray<AccessibleData>& aTree);
+
+ virtual void MaybeSendShowEvent(ShowEventData& aData, bool aFromUser)
+ { Unused << SendShowEvent(aData, aFromUser); }
+
+ void DetachDocument()
+ {
+ if (mDoc) {
+ mDoc->SetIPCDoc(nullptr);
+ mDoc = nullptr;
+ }
+ }
+
+ DocAccessible* mDoc;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_DocAccessibleChildBase_h
diff --git a/accessible/ipc/DocAccessibleParent.cpp b/accessible/ipc/DocAccessibleParent.cpp
new file mode 100644
index 000000000..0e9dfc080
--- /dev/null
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -0,0 +1,526 @@
+/* -*- 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 "DocAccessibleParent.h"
+#include "mozilla/a11y/Platform.h"
+#include "mozilla/dom/TabParent.h"
+#include "xpcAccessibleDocument.h"
+#include "xpcAccEvents.h"
+#include "nsAccUtils.h"
+#include "nsCoreUtils.h"
+
+namespace mozilla {
+namespace a11y {
+
+bool
+DocAccessibleParent::RecvShowEvent(const ShowEventData& aData,
+ const bool& aFromUser)
+{
+ if (mShutdown)
+ return true;
+
+ MOZ_DIAGNOSTIC_ASSERT(CheckDocTree());
+
+ if (aData.NewTree().IsEmpty()) {
+ NS_ERROR("no children being added");
+ return false;
+ }
+
+ ProxyAccessible* parent = GetAccessible(aData.ID());
+
+ // XXX This should really never happen, but sometimes we fail to fire the
+ // required show events.
+ if (!parent) {
+ NS_ERROR("adding child to unknown accessible");
+ return true;
+ }
+
+ uint32_t newChildIdx = aData.Idx();
+ if (newChildIdx > parent->ChildrenCount()) {
+ NS_ERROR("invalid index to add child at");
+ return true;
+ }
+
+ uint32_t consumed = AddSubtree(parent, aData.NewTree(), 0, newChildIdx);
+ MOZ_ASSERT(consumed == aData.NewTree().Length());
+
+ // XXX This shouldn't happen, but if we failed to add children then the below
+ // is pointless and can crash.
+ if (!consumed) {
+ return true;
+ }
+
+#ifdef DEBUG
+ for (uint32_t i = 0; i < consumed; i++) {
+ uint64_t id = aData.NewTree()[i].ID();
+ MOZ_ASSERT(mAccessibles.GetEntry(id));
+ }
+#endif
+
+ MOZ_DIAGNOSTIC_ASSERT(CheckDocTree());
+
+ ProxyAccessible* target = parent->ChildAt(newChildIdx);
+ ProxyShowHideEvent(target, parent, true, aFromUser);
+
+ if (!nsCoreUtils::AccEventObserversExist()) {
+ return true;
+ }
+
+ uint32_t type = nsIAccessibleEvent::EVENT_SHOW;
+ xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
+ xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+ nsIDOMNode* node = nullptr;
+ RefPtr<xpcAccEvent> event = new xpcAccEvent(type, xpcAcc, doc, node,
+ aFromUser);
+ nsCoreUtils::DispatchAccEvent(Move(event));
+
+ return true;
+}
+
+uint32_t
+DocAccessibleParent::AddSubtree(ProxyAccessible* aParent,
+ const nsTArray<a11y::AccessibleData>& aNewTree,
+ uint32_t aIdx, uint32_t aIdxInParent)
+{
+ if (aNewTree.Length() <= aIdx) {
+ NS_ERROR("bad index in serialized tree!");
+ return 0;
+ }
+
+ const AccessibleData& newChild = aNewTree[aIdx];
+ if (newChild.Role() > roles::LAST_ROLE) {
+ NS_ERROR("invalid role");
+ return 0;
+ }
+
+ if (mAccessibles.Contains(newChild.ID())) {
+ NS_ERROR("ID already in use");
+ return 0;
+ }
+
+ auto role = static_cast<a11y::role>(newChild.Role());
+
+ ProxyAccessible* newProxy =
+ new ProxyAccessible(newChild.ID(), aParent, this, role,
+ newChild.Interfaces());
+
+ aParent->AddChildAt(aIdxInParent, newProxy);
+ mAccessibles.PutEntry(newChild.ID())->mProxy = newProxy;
+ ProxyCreated(newProxy, newChild.Interfaces());
+
+#if defined(XP_WIN)
+ WrapperFor(newProxy)->SetID(newChild.MsaaID());
+#endif
+
+ uint32_t accessibles = 1;
+ uint32_t kids = newChild.ChildrenCount();
+ for (uint32_t i = 0; i < kids; i++) {
+ uint32_t consumed = AddSubtree(newProxy, aNewTree, aIdx + accessibles, i);
+ if (!consumed)
+ return 0;
+
+ accessibles += consumed;
+ }
+
+ MOZ_ASSERT(newProxy->ChildrenCount() == kids);
+
+ return accessibles;
+}
+
+bool
+DocAccessibleParent::RecvHideEvent(const uint64_t& aRootID,
+ const bool& aFromUser)
+{
+ if (mShutdown)
+ return true;
+
+ MOZ_DIAGNOSTIC_ASSERT(CheckDocTree());
+
+ // We shouldn't actually need this because mAccessibles shouldn't have an
+ // entry for the document itself, but it doesn't hurt to be explicit.
+ if (!aRootID) {
+ NS_ERROR("trying to hide entire document?");
+ return false;
+ }
+
+ ProxyEntry* rootEntry = mAccessibles.GetEntry(aRootID);
+ if (!rootEntry) {
+ NS_ERROR("invalid root being removed!");
+ return true;
+ }
+
+ ProxyAccessible* root = rootEntry->mProxy;
+ if (!root) {
+ NS_ERROR("invalid root being removed!");
+ return true;
+ }
+
+ ProxyAccessible* parent = root->Parent();
+ ProxyShowHideEvent(root, parent, false, aFromUser);
+
+ RefPtr<xpcAccHideEvent> event = nullptr;
+ if (nsCoreUtils::AccEventObserversExist()) {
+ uint32_t type = nsIAccessibleEvent::EVENT_HIDE;
+ xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(root);
+ xpcAccessibleGeneric* xpcParent = GetXPCAccessible(parent);
+ ProxyAccessible* next = root->NextSibling();
+ xpcAccessibleGeneric* xpcNext = next ? GetXPCAccessible(next) : nullptr;
+ ProxyAccessible* prev = root->PrevSibling();
+ xpcAccessibleGeneric* xpcPrev = prev ? GetXPCAccessible(prev) : nullptr;
+ xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+ nsIDOMNode* node = nullptr;
+ event = new xpcAccHideEvent(type, xpcAcc, doc, node, aFromUser, xpcParent,
+ xpcNext, xpcPrev);
+ }
+
+ parent->RemoveChild(root);
+ root->Shutdown();
+
+ MOZ_DIAGNOSTIC_ASSERT(CheckDocTree());
+
+ if (event) {
+ nsCoreUtils::DispatchAccEvent(Move(event));
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleParent::RecvEvent(const uint64_t& aID, const uint32_t& aEventType)
+{
+ ProxyAccessible* proxy = GetAccessible(aID);
+ if (!proxy) {
+ NS_ERROR("no proxy for event!");
+ return true;
+ }
+
+ ProxyEvent(proxy, aEventType);
+
+ if (!nsCoreUtils::AccEventObserversExist()) {
+ return true;
+ }
+
+ xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(proxy);
+ xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+ nsIDOMNode* node = nullptr;
+ bool fromUser = true; // XXX fix me
+ RefPtr<xpcAccEvent> event = new xpcAccEvent(aEventType, xpcAcc, doc, node,
+ fromUser);
+ nsCoreUtils::DispatchAccEvent(Move(event));
+
+ return true;
+}
+
+bool
+DocAccessibleParent::RecvStateChangeEvent(const uint64_t& aID,
+ const uint64_t& aState,
+ const bool& aEnabled)
+{
+ ProxyAccessible* target = GetAccessible(aID);
+ if (!target) {
+ NS_ERROR("we don't know about the target of a state change event!");
+ return true;
+ }
+
+ ProxyStateChangeEvent(target, aState, aEnabled);
+
+ if (!nsCoreUtils::AccEventObserversExist()) {
+ return true;
+ }
+
+ xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
+ xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+ uint32_t type = nsIAccessibleEvent::EVENT_STATE_CHANGE;
+ bool extra;
+ uint32_t state = nsAccUtils::To32States(aState, &extra);
+ bool fromUser = true; // XXX fix this
+ nsIDOMNode* node = nullptr; // XXX can we do better?
+ RefPtr<xpcAccStateChangeEvent> event =
+ new xpcAccStateChangeEvent(type, xpcAcc, doc, node, fromUser, state, extra,
+ aEnabled);
+ nsCoreUtils::DispatchAccEvent(Move(event));
+
+ return true;
+}
+
+bool
+DocAccessibleParent::RecvCaretMoveEvent(const uint64_t& aID, const int32_t& aOffset)
+{
+ ProxyAccessible* proxy = GetAccessible(aID);
+ if (!proxy) {
+ NS_ERROR("unknown caret move event target!");
+ return true;
+ }
+
+ ProxyCaretMoveEvent(proxy, aOffset);
+
+ if (!nsCoreUtils::AccEventObserversExist()) {
+ return true;
+ }
+
+ xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(proxy);
+ xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+ nsIDOMNode* node = nullptr;
+ bool fromUser = true; // XXX fix me
+ uint32_t type = nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED;
+ RefPtr<xpcAccCaretMoveEvent> event =
+ new xpcAccCaretMoveEvent(type, xpcAcc, doc, node, fromUser, aOffset);
+ nsCoreUtils::DispatchAccEvent(Move(event));
+
+ return true;
+}
+
+bool
+DocAccessibleParent::RecvTextChangeEvent(const uint64_t& aID,
+ const nsString& aStr,
+ const int32_t& aStart,
+ const uint32_t& aLen,
+ const bool& aIsInsert,
+ const bool& aFromUser)
+{
+ ProxyAccessible* target = GetAccessible(aID);
+ if (!target) {
+ NS_ERROR("text change event target is unknown!");
+ return true;
+ }
+
+ ProxyTextChangeEvent(target, aStr, aStart, aLen, aIsInsert, aFromUser);
+
+ if (!nsCoreUtils::AccEventObserversExist()) {
+ return true;
+ }
+
+ xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
+ xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+ uint32_t type = aIsInsert ? nsIAccessibleEvent::EVENT_TEXT_INSERTED :
+ nsIAccessibleEvent::EVENT_TEXT_REMOVED;
+ nsIDOMNode* node = nullptr;
+ RefPtr<xpcAccTextChangeEvent> event =
+ new xpcAccTextChangeEvent(type, xpcAcc, doc, node, aFromUser, aStart, aLen,
+ aIsInsert, aStr);
+ nsCoreUtils::DispatchAccEvent(Move(event));
+
+ return true;
+}
+
+bool
+DocAccessibleParent::RecvSelectionEvent(const uint64_t& aID,
+ const uint64_t& aWidgetID,
+ const uint32_t& aType)
+{
+ ProxyAccessible* target = GetAccessible(aID);
+ ProxyAccessible* widget = GetAccessible(aWidgetID);
+ if (!target || !widget) {
+ NS_ERROR("invalid id in selection event");
+ return true;
+ }
+
+ ProxySelectionEvent(target, widget, aType);
+ if (!nsCoreUtils::AccEventObserversExist()) {
+ return true;
+ }
+ xpcAccessibleGeneric* xpcTarget = GetXPCAccessible(target);
+ xpcAccessibleDocument* xpcDoc = GetAccService()->GetXPCDocument(this);
+ RefPtr<xpcAccEvent> event = new xpcAccEvent(aType, xpcTarget, xpcDoc,
+ nullptr, false);
+ nsCoreUtils::DispatchAccEvent(Move(event));
+
+ return true;
+}
+
+bool
+DocAccessibleParent::RecvRoleChangedEvent(const uint32_t& aRole)
+{
+ if (aRole >= roles::LAST_ROLE) {
+ NS_ERROR("child sent bad role in RoleChangedEvent");
+ return false;
+ }
+
+ mRole = static_cast<a11y::role>(aRole);
+ return true;
+}
+
+bool
+DocAccessibleParent::RecvBindChildDoc(PDocAccessibleParent* aChildDoc, const uint64_t& aID)
+{
+ // One document should never directly be the child of another.
+ // We should always have at least an outer doc accessible in between.
+ MOZ_ASSERT(aID);
+ if (!aID)
+ return false;
+
+ MOZ_DIAGNOSTIC_ASSERT(CheckDocTree());
+
+ auto childDoc = static_cast<DocAccessibleParent*>(aChildDoc);
+ childDoc->Unbind();
+ bool result = AddChildDoc(childDoc, aID, false);
+ MOZ_ASSERT(result);
+ MOZ_DIAGNOSTIC_ASSERT(CheckDocTree());
+ return result;
+}
+
+bool
+DocAccessibleParent::AddChildDoc(DocAccessibleParent* aChildDoc,
+ uint64_t aParentID, bool aCreating)
+{
+ // We do not use GetAccessible here because we want to be sure to not get the
+ // document it self.
+ ProxyEntry* e = mAccessibles.GetEntry(aParentID);
+ if (!e)
+ return false;
+
+ ProxyAccessible* outerDoc = e->mProxy;
+ MOZ_ASSERT(outerDoc);
+
+ // OuterDocAccessibles are expected to only have a document as a child.
+ // However for compatibility we tolerate replacing one document with another
+ // here.
+ if (outerDoc->ChildrenCount() > 1 ||
+ (outerDoc->ChildrenCount() == 1 && !outerDoc->ChildAt(0)->IsDoc())) {
+ return false;
+ }
+
+ aChildDoc->mParent = outerDoc;
+ outerDoc->SetChildDoc(aChildDoc);
+ mChildDocs.AppendElement(aChildDoc);
+ aChildDoc->mParentDoc = this;
+
+ if (aCreating) {
+ ProxyCreated(aChildDoc, Interfaces::DOCUMENT | Interfaces::HYPERTEXT);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleParent::RecvShutdown()
+{
+ Destroy();
+
+ if (!static_cast<dom::TabParent*>(Manager())->IsDestroyed()) {
+ return PDocAccessibleParent::Send__delete__(this);
+ }
+
+ return true;
+}
+
+void
+DocAccessibleParent::Destroy()
+{
+ NS_ASSERTION(mChildDocs.IsEmpty(),
+ "why weren't the child docs destroyed already?");
+ MOZ_ASSERT(!mShutdown);
+ mShutdown = true;
+
+ uint32_t childDocCount = mChildDocs.Length();
+ for (uint32_t i = childDocCount - 1; i < childDocCount; i--)
+ mChildDocs[i]->Destroy();
+
+ for (auto iter = mAccessibles.Iter(); !iter.Done(); iter.Next()) {
+ MOZ_ASSERT(iter.Get()->mProxy != this);
+ ProxyDestroyed(iter.Get()->mProxy);
+ iter.Remove();
+ }
+
+ DocManager::NotifyOfRemoteDocShutdown(this);
+ ProxyDestroyed(this);
+ if (mParentDoc)
+ mParentDoc->RemoveChildDoc(this);
+ else if (IsTopLevel())
+ GetAccService()->RemoteDocShutdown(this);
+}
+
+bool
+DocAccessibleParent::CheckDocTree() const
+{
+ size_t childDocs = mChildDocs.Length();
+ for (size_t i = 0; i < childDocs; i++) {
+ if (!mChildDocs[i] || mChildDocs[i]->mParentDoc != this)
+ return false;
+
+ if (!mChildDocs[i]->CheckDocTree()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+xpcAccessibleGeneric*
+DocAccessibleParent::GetXPCAccessible(ProxyAccessible* aProxy)
+{
+ xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+ MOZ_ASSERT(doc);
+
+ return doc->GetXPCAccessible(aProxy);
+}
+
+#if defined(XP_WIN)
+/**
+ * @param aCOMProxy COM Proxy to the document in the content process.
+ */
+void
+DocAccessibleParent::SetCOMProxy(const RefPtr<IAccessible>& aCOMProxy)
+{
+ SetCOMInterface(aCOMProxy);
+
+ // Make sure that we're not racing with a tab shutdown
+ auto tab = static_cast<dom::TabParent*>(Manager());
+ MOZ_ASSERT(tab);
+ if (tab->IsDestroyed()) {
+ return;
+ }
+
+ Accessible* outerDoc = OuterDocOfRemoteBrowser();
+ MOZ_ASSERT(outerDoc);
+
+ IAccessible* rawNative = nullptr;
+ if (outerDoc) {
+ outerDoc->GetNativeInterface((void**) &rawNative);
+ MOZ_ASSERT(rawNative);
+ }
+
+ IAccessibleHolder::COMPtrType ptr(rawNative);
+ IAccessibleHolder holder(Move(ptr));
+ Unused << SendParentCOMProxy(holder);
+}
+
+bool
+DocAccessibleParent::RecvGetWindowedPluginIAccessible(
+ const WindowsHandle& aHwnd, IAccessibleHolder* aPluginCOMProxy)
+{
+#if defined(MOZ_CONTENT_SANDBOX)
+ // We don't actually want the accessible object for aHwnd, but rather the
+ // one that belongs to its child (see HTMLWin32ObjectAccessible).
+ HWND childWnd = ::GetWindow(reinterpret_cast<HWND>(aHwnd), GW_CHILD);
+ if (!childWnd) {
+ // We're seeing this in the wild - the plugin is windowed but we no longer
+ // have a window.
+ return true;
+ }
+
+ IAccessible* rawAccPlugin = nullptr;
+ HRESULT hr = ::AccessibleObjectFromWindow(childWnd, OBJID_WINDOW,
+ IID_IAccessible,
+ (void**)&rawAccPlugin);
+ if (FAILED(hr)) {
+ // This might happen if the plugin doesn't handle WM_GETOBJECT properly.
+ // We should not consider that a failure.
+ return true;
+ }
+
+ aPluginCOMProxy->Set(IAccessibleHolder::COMPtrType(rawAccPlugin));
+
+ return true;
+#else
+ return false;
+#endif
+}
+
+#endif // defined(XP_WIN)
+
+} // a11y
+} // mozilla
diff --git a/accessible/ipc/DocAccessibleParent.h b/accessible/ipc/DocAccessibleParent.h
new file mode 100644
index 000000000..3686e8a2f
--- /dev/null
+++ b/accessible/ipc/DocAccessibleParent.h
@@ -0,0 +1,198 @@
+/* -*- 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_DocAccessibleParent_h
+#define mozilla_a11y_DocAccessibleParent_h
+
+#include "nsAccessibilityService.h"
+#include "mozilla/a11y/PDocAccessibleParent.h"
+#include "mozilla/a11y/ProxyAccessible.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+namespace a11y {
+
+class xpcAccessibleGeneric;
+
+/*
+ * These objects live in the main process and comunicate with and represent
+ * an accessible document in a content process.
+ */
+class DocAccessibleParent : public ProxyAccessible,
+ public PDocAccessibleParent
+{
+public:
+ DocAccessibleParent() :
+ ProxyAccessible(this), mParentDoc(nullptr),
+ mTopLevel(false), mShutdown(false)
+ { MOZ_COUNT_CTOR_INHERITED(DocAccessibleParent, ProxyAccessible); }
+ ~DocAccessibleParent()
+ {
+ MOZ_COUNT_DTOR_INHERITED(DocAccessibleParent, ProxyAccessible);
+ MOZ_ASSERT(mChildDocs.Length() == 0);
+ MOZ_ASSERT(!ParentDoc());
+ }
+
+ void SetTopLevel() { mTopLevel = true; }
+ bool IsTopLevel() const { return mTopLevel; }
+
+ bool IsShutdown() const { return mShutdown; }
+
+ /*
+ * Called when a message from a document in a child process notifies the main
+ * process it is firing an event.
+ */
+ virtual bool RecvEvent(const uint64_t& aID, const uint32_t& aType)
+ override;
+
+ virtual bool RecvShowEvent(const ShowEventData& aData, const bool& aFromUser)
+ override;
+ virtual bool RecvHideEvent(const uint64_t& aRootID, const bool& aFromUser)
+ override;
+ virtual bool RecvStateChangeEvent(const uint64_t& aID,
+ const uint64_t& aState,
+ const bool& aEnabled) override final;
+
+ virtual bool RecvCaretMoveEvent(const uint64_t& aID, const int32_t& aOffset)
+ override final;
+
+ virtual bool RecvTextChangeEvent(const uint64_t& aID, const nsString& aStr,
+ const int32_t& aStart, const uint32_t& aLen,
+ const bool& aIsInsert,
+ const bool& aFromUser) override;
+
+ virtual bool RecvSelectionEvent(const uint64_t& aID,
+ const uint64_t& aWidgetID,
+ const uint32_t& aType) override;
+
+ virtual bool RecvRoleChangedEvent(const uint32_t& aRole) override final;
+
+ virtual bool RecvBindChildDoc(PDocAccessibleParent* aChildDoc, const uint64_t& aID) override;
+
+ void Unbind()
+ {
+ if (DocAccessibleParent* parent = ParentDoc()) {
+ parent->RemoveChildDoc(this);
+ }
+
+ mParent = nullptr;
+ }
+
+ virtual bool RecvShutdown() override;
+ void Destroy();
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override
+ {
+ MOZ_DIAGNOSTIC_ASSERT(CheckDocTree());
+ if (!mShutdown)
+ Destroy();
+ }
+
+ /*
+ * Return the main processes representation of the parent document (if any)
+ * of the document this object represents.
+ */
+ DocAccessibleParent* ParentDoc() const { return mParentDoc; }
+
+ /*
+ * Called when a document in a content process notifies the main process of a
+ * new child document.
+ */
+ bool AddChildDoc(DocAccessibleParent* aChildDoc, uint64_t aParentID,
+ bool aCreating = true);
+
+ /*
+ * Called when the document in the content process this object represents
+ * notifies the main process a child document has been removed.
+ */
+ void RemoveChildDoc(DocAccessibleParent* aChildDoc)
+ {
+ aChildDoc->Parent()->ClearChildDoc(aChildDoc);
+ mChildDocs.RemoveElement(aChildDoc);
+ aChildDoc->mParentDoc = nullptr;
+ MOZ_ASSERT(aChildDoc->mChildDocs.Length() == 0);
+ }
+
+ void RemoveAccessible(ProxyAccessible* aAccessible)
+ {
+ MOZ_DIAGNOSTIC_ASSERT(mAccessibles.GetEntry(aAccessible->ID()));
+ mAccessibles.RemoveEntry(aAccessible->ID());
+ }
+
+ /**
+ * Return the accessible for given id.
+ */
+ ProxyAccessible* GetAccessible(uintptr_t aID)
+ {
+ if (!aID)
+ return this;
+
+ ProxyEntry* e = mAccessibles.GetEntry(aID);
+ return e ? e->mProxy : nullptr;
+ }
+
+ const ProxyAccessible* GetAccessible(uintptr_t aID) const
+ { return const_cast<DocAccessibleParent*>(this)->GetAccessible(aID); }
+
+ size_t ChildDocCount() const { return mChildDocs.Length(); }
+ const DocAccessibleParent* ChildDocAt(size_t aIdx) const
+ { return mChildDocs[aIdx]; }
+
+#if defined(XP_WIN)
+ void SetCOMProxy(const RefPtr<IAccessible>& aCOMProxy);
+
+ virtual bool RecvGetWindowedPluginIAccessible(
+ const WindowsHandle& aHwnd, IAccessibleHolder* aPluginCOMProxy) override;
+#endif
+
+private:
+
+ class ProxyEntry : public PLDHashEntryHdr
+ {
+ public:
+ explicit ProxyEntry(const void*) : mProxy(nullptr) {}
+ ProxyEntry(ProxyEntry&& aOther) :
+ mProxy(aOther.mProxy) { aOther.mProxy = nullptr; }
+ ~ProxyEntry() { delete mProxy; }
+
+ typedef uint64_t KeyType;
+ typedef const void* KeyTypePointer;
+
+ bool KeyEquals(const void* aKey) const
+ { return mProxy->ID() == (uint64_t)aKey; }
+
+ static const void* KeyToPointer(uint64_t aKey) { return (void*)aKey; }
+
+ static PLDHashNumber HashKey(const void* aKey) { return (uint64_t)aKey; }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ ProxyAccessible* mProxy;
+ };
+
+ uint32_t AddSubtree(ProxyAccessible* aParent,
+ const nsTArray<AccessibleData>& aNewTree, uint32_t aIdx,
+ uint32_t aIdxInParent);
+ MOZ_MUST_USE bool CheckDocTree() const;
+ xpcAccessibleGeneric* GetXPCAccessible(ProxyAccessible* aProxy);
+
+ nsTArray<DocAccessibleParent*> mChildDocs;
+ DocAccessibleParent* mParentDoc;
+
+ /*
+ * Conceptually this is a map from IDs to proxies, but we store the ID in the
+ * proxy object so we can't use a real map.
+ */
+ nsTHashtable<ProxyEntry> mAccessibles;
+ bool mTopLevel;
+ bool mShutdown;
+};
+
+}
+}
+
+#endif
diff --git a/accessible/ipc/IPCTypes.h b/accessible/ipc/IPCTypes.h
new file mode 100644
index 000000000..0e77c6348
--- /dev/null
+++ b/accessible/ipc/IPCTypes.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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_IPCTypes_h
+#define mozilla_a11y_IPCTypes_h
+
+/**
+ * Since IPDL does not support preprocessing, this header file allows us to
+ * define types used by PDocAccessible differently depending on platform.
+ */
+
+#if defined(XP_WIN) && defined(ACCESSIBILITY)
+
+// So that we don't include a bunch of other Windows junk.
+#if !defined(COM_NO_WINDOWS_H)
+#define COM_NO_WINDOWS_H
+#endif // !defined(COM_NO_WINDOWS_H)
+
+// COM headers pull in MSXML which conflicts with our own XMLDocument class.
+// This define excludes those conflicting definitions.
+#if !defined(__XMLDocument_FWD_DEFINED__)
+#define __XMLDocument_FWD_DEFINED__
+#endif // !defined(__XMLDocument_FWD_DEFINED__)
+
+#include "mozilla/a11y/COMPtrTypes.h"
+
+// This define in rpcndr.h messes up our code, so we must undefine it after
+// COMPtrTypes.h has been included.
+#if defined(small)
+#undef small
+#endif // defined(small)
+
+#else
+
+namespace mozilla {
+namespace a11y {
+
+typedef uint32_t IAccessibleHolder;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // defined(XP_WIN) && defined(ACCESSIBILITY)
+
+#endif // mozilla_a11y_IPCTypes_h
+
diff --git a/accessible/ipc/ProxyAccessibleBase.cpp b/accessible/ipc/ProxyAccessibleBase.cpp
new file mode 100644
index 000000000..8f1a2b7ed
--- /dev/null
+++ b/accessible/ipc/ProxyAccessibleBase.cpp
@@ -0,0 +1,175 @@
+/* -*- 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 "DocAccessible.h"
+#include "mozilla/a11y/DocAccessibleParent.h"
+#include "mozilla/a11y/DocManager.h"
+#include "mozilla/a11y/Platform.h"
+#include "mozilla/a11y/ProxyAccessibleBase.h"
+#include "mozilla/a11y/ProxyAccessible.h"
+#include "mozilla/a11y/Role.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/Unused.h"
+#include "RelationType.h"
+#include "xpcAccessibleDocument.h"
+
+namespace mozilla {
+namespace a11y {
+
+template <class Derived>
+void
+ProxyAccessibleBase<Derived>::Shutdown()
+{
+ MOZ_DIAGNOSTIC_ASSERT(!IsDoc());
+ NS_ASSERTION(!mOuterDoc, "Why do we still have a child doc?");
+ xpcAccessibleDocument* xpcDoc =
+ GetAccService()->GetCachedXPCDocument(Document());
+ if (xpcDoc) {
+ xpcDoc->NotifyOfShutdown(static_cast<Derived*>(this));
+ }
+
+ // XXX Ideally this wouldn't be necessary, but it seems OuterDoc accessibles
+ // can be destroyed before the doc they own.
+ if (!mOuterDoc) {
+ uint32_t childCount = mChildren.Length();
+ for (uint32_t idx = 0; idx < childCount; idx++)
+ mChildren[idx]->Shutdown();
+ } else {
+ if (mChildren.Length() != 1)
+ MOZ_CRASH("outer doc doesn't own adoc!");
+
+ mChildren[0]->AsDoc()->Unbind();
+ }
+
+ mChildren.Clear();
+ ProxyDestroyed(static_cast<Derived*>(this));
+ mDoc->RemoveAccessible(static_cast<Derived*>(this));
+}
+
+template <class Derived>
+void
+ProxyAccessibleBase<Derived>::SetChildDoc(DocAccessibleParent* aChildDoc)
+{
+ // DocAccessibleParent::AddChildDoc tolerates replacing one document with
+ // another. We must reflect that here.
+ MOZ_ASSERT(aChildDoc);
+ MOZ_ASSERT(mChildren.Length() <= 1);
+ if (mChildren.IsEmpty()) {
+ mChildren.AppendElement(aChildDoc);
+ } else {
+ mChildren.ReplaceElementAt(0, aChildDoc);
+ }
+ mOuterDoc = true;
+}
+
+template <class Derived>
+void
+ProxyAccessibleBase<Derived>::ClearChildDoc(DocAccessibleParent* aChildDoc)
+{
+ MOZ_ASSERT(aChildDoc);
+ // This is possible if we're replacing one document with another: Doc 1
+ // has not had a chance to remove itself, but was already replaced by Doc 2
+ // in SetChildDoc(). This could result in two subsequent calls to
+ // ClearChildDoc() even though mChildren.Length() == 1.
+ MOZ_ASSERT(mChildren.Length() <= 1);
+ if (mChildren.RemoveElement(aChildDoc)) {
+ mOuterDoc = false;
+ }
+}
+
+template <class Derived>
+bool
+ProxyAccessibleBase<Derived>::MustPruneChildren() const
+{
+ // this is the equivalent to nsAccUtils::MustPrune for proxies and should be
+ // kept in sync with that.
+ if (mRole != roles::MENUITEM && mRole != roles::COMBOBOX_OPTION
+ && mRole != roles::OPTION && mRole != roles::ENTRY
+ && mRole != roles::FLAT_EQUATION && mRole != roles::PASSWORD_TEXT
+ && mRole != roles::PUSHBUTTON && mRole != roles::TOGGLE_BUTTON
+ && mRole != roles::GRAPHIC && mRole != roles::SLIDER
+ && mRole != roles::PROGRESSBAR && mRole != roles::SEPARATOR)
+ return false;
+
+ if (mChildren.Length() != 1)
+ return false;
+
+ return mChildren[0]->Role() == roles::TEXT_LEAF
+ || mChildren[0]->Role() == roles::STATICTEXT;
+}
+
+template <class Derived>
+uint32_t
+ProxyAccessibleBase<Derived>::EmbeddedChildCount() const
+{
+ size_t count = 0, kids = mChildren.Length();
+ for (size_t i = 0; i < kids; i++) {
+ if (mChildren[i]->IsEmbeddedObject()) {
+ count++;
+ }
+ }
+
+ return count;
+}
+
+template <class Derived>
+int32_t
+ProxyAccessibleBase<Derived>::IndexOfEmbeddedChild(const Derived* aChild)
+{
+ size_t index = 0, kids = mChildren.Length();
+ for (size_t i = 0; i < kids; i++) {
+ if (mChildren[i]->IsEmbeddedObject()) {
+ if (mChildren[i] == aChild) {
+ return index;
+ }
+
+ index++;
+ }
+ }
+
+ return -1;
+}
+
+template <class Derived>
+Derived*
+ProxyAccessibleBase<Derived>::EmbeddedChildAt(size_t aChildIdx)
+{
+ size_t index = 0, kids = mChildren.Length();
+ for (size_t i = 0; i < kids; i++) {
+ if (!mChildren[i]->IsEmbeddedObject()) {
+ continue;
+ }
+
+ if (index == aChildIdx) {
+ return mChildren[i];
+ }
+
+ index++;
+ }
+
+ return nullptr;
+}
+
+template <class Derived>
+Accessible*
+ProxyAccessibleBase<Derived>::OuterDocOfRemoteBrowser() const
+{
+ auto tab = static_cast<dom::TabParent*>(mDoc->Manager());
+ dom::Element* frame = tab->GetOwnerElement();
+ NS_ASSERTION(frame, "why isn't the tab in a frame!");
+ if (!frame)
+ return nullptr;
+
+ DocAccessible* chromeDoc = GetExistingDocAccessible(frame->OwnerDoc());
+
+ return chromeDoc ? chromeDoc->GetAccessible(frame) : nullptr;
+}
+
+template class ProxyAccessibleBase<ProxyAccessible>;
+
+} // namespace a11y
+} // namespace mozilla
diff --git a/accessible/ipc/ProxyAccessibleBase.h b/accessible/ipc/ProxyAccessibleBase.h
new file mode 100644
index 000000000..bea669ffa
--- /dev/null
+++ b/accessible/ipc/ProxyAccessibleBase.h
@@ -0,0 +1,211 @@
+/* -*- 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_ProxyAccessibleBase_h
+#define mozilla_a11y_ProxyAccessibleBase_h
+
+#include "mozilla/a11y/Role.h"
+#include "nsIAccessibleText.h"
+#include "nsIAccessibleTypes.h"
+#include "Accessible.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsRect.h"
+#include "Accessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+class Attribute;
+class DocAccessibleParent;
+class ProxyAccessible;
+enum class RelationType;
+
+enum Interfaces
+{
+ HYPERTEXT = 1,
+ HYPERLINK = 1 << 1,
+ IMAGE = 1 << 2,
+ VALUE = 1 << 3,
+ TABLE = 1 << 4,
+ TABLECELL = 1 << 5,
+ DOCUMENT = 1 << 6,
+ SELECTION = 1 << 7,
+ ACTION = 1 << 8,
+};
+
+template <class Derived>
+class ProxyAccessibleBase
+{
+public:
+ ~ProxyAccessibleBase()
+ {
+ MOZ_ASSERT(!mWrapper);
+ }
+
+ void AddChildAt(uint32_t aIdx, Derived* aChild)
+ { mChildren.InsertElementAt(aIdx, aChild); }
+
+ uint32_t ChildrenCount() const { return mChildren.Length(); }
+ Derived* ChildAt(uint32_t aIdx) const { return mChildren[aIdx]; }
+ Derived* FirstChild() const
+ { return mChildren.Length() ? mChildren[0] : nullptr; }
+ Derived* LastChild() const
+ { return mChildren.Length() ? mChildren[mChildren.Length() - 1] : nullptr; }
+ Derived* PrevSibling() const
+ {
+ size_t idx = IndexInParent();
+ return idx > 0 ? Parent()->mChildren[idx - 1] : nullptr;
+ }
+ Derived* NextSibling() const
+ {
+ size_t idx = IndexInParent();
+ return idx + 1 < Parent()->mChildren.Length() ? Parent()->mChildren[idx + 1]
+ : nullptr;
+ }
+
+ // XXX evaluate if this is fast enough.
+ size_t IndexInParent() const { return
+ Parent()->mChildren.IndexOf(static_cast<const Derived*>(this)); }
+ uint32_t EmbeddedChildCount() const;
+ int32_t IndexOfEmbeddedChild(const Derived* aChild);
+ Derived* EmbeddedChildAt(size_t aChildIdx);
+ bool MustPruneChildren() const;
+
+ void Shutdown();
+
+ void SetChildDoc(DocAccessibleParent* aChildDoc);
+ void ClearChildDoc(DocAccessibleParent* aChildDoc);
+
+ /**
+ * Remove The given child.
+ */
+ void RemoveChild(Derived* aChild)
+ { mChildren.RemoveElement(aChild); }
+
+ /**
+ * Return the proxy for the parent of the wrapped accessible.
+ */
+ Derived* Parent() const { return mParent; }
+
+ Accessible* OuterDocOfRemoteBrowser() const;
+
+ /**
+ * Get the role of the accessible we're proxying.
+ */
+ role Role() const { return mRole; }
+
+ /**
+ * Return true if this is an embedded object.
+ */
+ bool IsEmbeddedObject() const
+ {
+ role role = Role();
+ return role != roles::TEXT_LEAF &&
+ role != roles::WHITESPACE &&
+ role != roles::STATICTEXT;
+ }
+
+ /**
+ * Allow the platform to store a pointers worth of data on us.
+ */
+ uintptr_t GetWrapper() const { return mWrapper; }
+ void SetWrapper(uintptr_t aWrapper) { mWrapper = aWrapper; }
+
+ /*
+ * Return the ID of the accessible being proxied.
+ */
+ uint64_t ID() const { return mID; }
+
+ /**
+ * Return the document containing this proxy, or the proxy itself if it is a
+ * document.
+ */
+ DocAccessibleParent* Document() const { return mDoc; }
+
+ /**
+ * Return true if this proxy is a DocAccessibleParent.
+ */
+ bool IsDoc() const { return mIsDoc; }
+ DocAccessibleParent* AsDoc() const { return IsDoc() ? mDoc : nullptr; }
+
+ // XXX checking mRole alone may not result in same behavior as Accessibles
+ // due to ARIA roles. See bug 1210477.
+ inline bool IsTable() const
+ {
+ return mRole == roles::TABLE || mRole == roles::MATHML_TABLE;
+ }
+ inline bool IsTableRow() const
+ {
+ return (mRole == roles::ROW ||
+ mRole == roles::MATHML_TABLE_ROW ||
+ mRole == roles::MATHML_LABELED_ROW);
+ }
+ inline bool IsTableCell() const
+ {
+ return (mRole == roles::CELL ||
+ mRole == roles::COLUMNHEADER ||
+ mRole == roles::ROWHEADER ||
+ mRole == roles::GRID_CELL ||
+ mRole == roles::MATHML_CELL);
+ }
+
+protected:
+ ProxyAccessibleBase(uint64_t aID, Derived* aParent,
+ DocAccessibleParent* aDoc, role aRole,
+ uint32_t aInterfaces)
+ : mParent(aParent)
+ , mDoc(aDoc)
+ , mWrapper(0)
+ , mID(aID)
+ , mRole(aRole)
+ , mOuterDoc(false)
+ , mIsDoc(false)
+ , mHasValue(aInterfaces & Interfaces::VALUE)
+ , mIsHyperLink(aInterfaces & Interfaces::HYPERLINK)
+ , mIsHyperText(aInterfaces & Interfaces::HYPERTEXT)
+ {
+ }
+
+ explicit ProxyAccessibleBase(DocAccessibleParent* aThisAsDoc) :
+ mParent(nullptr), mDoc(aThisAsDoc), mWrapper(0), mID(0),
+ mRole(roles::DOCUMENT), mOuterDoc(false), mIsDoc(true), mHasValue(false),
+ mIsHyperLink(false), mIsHyperText(false)
+ {}
+
+protected:
+ Derived* mParent;
+
+private:
+ friend Derived;
+
+ nsTArray<Derived*> mChildren;
+ DocAccessibleParent* mDoc;
+ uintptr_t mWrapper;
+ uint64_t mID;
+
+protected:
+ // XXX DocAccessibleParent gets to change this to change the role of
+ // documents.
+ role mRole : 27;
+
+private:
+ bool mOuterDoc : 1;
+
+public:
+ const bool mIsDoc: 1;
+ const bool mHasValue: 1;
+ const bool mIsHyperLink: 1;
+ const bool mIsHyperText: 1;
+};
+
+extern template class ProxyAccessibleBase<ProxyAccessible>;
+
+}
+}
+
+#endif
diff --git a/accessible/ipc/ProxyAccessibleShared.h b/accessible/ipc/ProxyAccessibleShared.h
new file mode 100644
index 000000000..e940d72ed
--- /dev/null
+++ b/accessible/ipc/ProxyAccessibleShared.h
@@ -0,0 +1,277 @@
+/* -*- 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_ProxyAccessibleShared_h
+#define mozilla_a11y_ProxyAccessibleShared_h
+
+/**
+ * These are function declarations shared between win/ProxyAccessible.h and
+ * other/ProxyAccessible.h.
+ */
+
+/*
+ * Return the states for the proxied accessible.
+ */
+uint64_t State() const;
+
+/*
+ * Return the native states for the proxied accessible.
+ */
+uint64_t NativeState() const;
+
+/*
+ * Set aName to the name of the proxied accessible.
+ */
+void Name(nsString& aName) const;
+
+/*
+ * Set aValue to the value of the proxied accessible.
+ */
+void Value(nsString& aValue) const;
+
+/*
+ * Set aHelp to the help string of the proxied accessible.
+ */
+void Help(nsString& aHelp) const;
+
+/**
+ * Set aDesc to the description of the proxied accessible.
+ */
+void Description(nsString& aDesc) const;
+
+/**
+ * Get the set of attributes on the proxied accessible.
+ */
+void Attributes(nsTArray<Attribute> *aAttrs) const;
+
+/**
+ * Return set of targets of given relation type.
+ */
+nsTArray<ProxyAccessible*> RelationByType(RelationType aType) const;
+
+/**
+ * Get all relations for this accessible.
+ */
+void Relations(nsTArray<RelationType>* aTypes,
+ nsTArray<nsTArray<ProxyAccessible*>>* aTargetSets) const;
+
+bool IsSearchbox() const;
+
+nsIAtom* LandmarkRole() const;
+
+nsIAtom* ARIARoleAtom() const;
+
+int32_t GetLevelInternal();
+void ScrollTo(uint32_t aScrollType);
+void ScrollToPoint(uint32_t aScrollType, int32_t aX, int32_t aY);
+
+int32_t CaretLineNumber();
+int32_t CaretOffset();
+void SetCaretOffset(int32_t aOffset);
+
+int32_t CharacterCount();
+int32_t SelectionCount();
+
+/**
+ * Get the text between the given offsets.
+ */
+bool TextSubstring(int32_t aStartOffset, int32_t aEndOfset,
+ nsString& aText) const;
+
+void GetTextAfterOffset(int32_t aOffset, AccessibleTextBoundary aBoundaryType,
+ nsString& aText, int32_t* aStartOffset,
+ int32_t* aEndOffset);
+
+void GetTextAtOffset(int32_t aOffset, AccessibleTextBoundary aBoundaryType,
+ nsString& aText, int32_t* aStartOffset,
+ int32_t* aEndOffset);
+
+void GetTextBeforeOffset(int32_t aOffset, AccessibleTextBoundary aBoundaryType,
+ nsString& aText, int32_t* aStartOffset,
+ int32_t* aEndOffset);
+
+char16_t CharAt(int32_t aOffset);
+
+void TextAttributes(bool aIncludeDefAttrs,
+ const int32_t aOffset,
+ nsTArray<Attribute>* aAttributes,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset);
+void DefaultTextAttributes(nsTArray<Attribute>* aAttrs);
+
+nsIntRect TextBounds(int32_t aStartOffset, int32_t aEndOffset,
+ uint32_t aCoordType = nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE);
+
+nsIntRect CharBounds(int32_t aOffset, uint32_t aCoordType);
+
+int32_t OffsetAtPoint(int32_t aX, int32_t aY, uint32_t aCoordType);
+
+bool SelectionBoundsAt(int32_t aSelectionNum,
+ nsString& aData,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset);
+
+bool SetSelectionBoundsAt(int32_t aSelectionNum,
+ int32_t aStartOffset,
+ int32_t aEndOffset);
+
+bool AddToSelection(int32_t aStartOffset,
+ int32_t aEndOffset);
+
+bool RemoveFromSelection(int32_t aSelectionNum);
+
+void ScrollSubstringTo(int32_t aStartOffset, int32_t aEndOffset,
+ uint32_t aScrollType);
+
+void ScrollSubstringToPoint(int32_t aStartOffset,
+ int32_t aEndOffset,
+ uint32_t aCoordinateType,
+ int32_t aX, int32_t aY);
+
+void Text(nsString* aText);
+
+void ReplaceText(const nsString& aText);
+
+bool InsertText(const nsString& aText, int32_t aPosition);
+
+bool CopyText(int32_t aStartPos, int32_t aEndPos);
+
+bool CutText(int32_t aStartPos, int32_t aEndPos);
+
+bool DeleteText(int32_t aStartPos, int32_t aEndPos);
+
+bool PasteText(int32_t aPosition);
+
+nsIntPoint ImagePosition(uint32_t aCoordType);
+
+nsIntSize ImageSize();
+
+uint32_t StartOffset(bool* aOk);
+
+uint32_t EndOffset(bool* aOk);
+
+bool IsLinkValid();
+
+uint32_t AnchorCount(bool* aOk);
+
+void AnchorURIAt(uint32_t aIndex, nsCString& aURI, bool* aOk);
+
+ProxyAccessible* AnchorAt(uint32_t aIndex);
+
+uint32_t LinkCount();
+
+ProxyAccessible* LinkAt(const uint32_t& aIndex);
+
+int32_t LinkIndexOf(ProxyAccessible* aLink);
+
+int32_t LinkIndexAtOffset(uint32_t aOffset);
+
+ProxyAccessible* TableOfACell();
+
+uint32_t ColIdx();
+
+uint32_t RowIdx();
+
+void GetPosition(uint32_t* aColIdx, uint32_t* aRowIdx);
+
+uint32_t ColExtent();
+
+uint32_t RowExtent();
+
+void GetColRowExtents(uint32_t* aColIdx, uint32_t* aRowIdx,
+ uint32_t* aColExtent, uint32_t* aRowExtent);
+
+void ColHeaderCells(nsTArray<ProxyAccessible*>* aCells);
+
+void RowHeaderCells(nsTArray<ProxyAccessible*>* aCells);
+
+bool IsCellSelected();
+
+ProxyAccessible* TableCaption();
+void TableSummary(nsString& aSummary);
+uint32_t TableColumnCount();
+uint32_t TableRowCount();
+ProxyAccessible* TableCellAt(uint32_t aRow, uint32_t aCol);
+int32_t TableCellIndexAt(uint32_t aRow, uint32_t aCol);
+int32_t TableColumnIndexAt(uint32_t aCellIndex);
+int32_t TableRowIndexAt(uint32_t aCellIndex);
+void TableRowAndColumnIndicesAt(uint32_t aCellIndex,
+ int32_t* aRow, int32_t* aCol);
+uint32_t TableColumnExtentAt(uint32_t aRow, uint32_t aCol);
+uint32_t TableRowExtentAt(uint32_t aRow, uint32_t aCol);
+void TableColumnDescription(uint32_t aCol, nsString& aDescription);
+void TableRowDescription(uint32_t aRow, nsString& aDescription);
+bool TableColumnSelected(uint32_t aCol);
+bool TableRowSelected(uint32_t aRow);
+bool TableCellSelected(uint32_t aRow, uint32_t aCol);
+uint32_t TableSelectedCellCount();
+uint32_t TableSelectedColumnCount();
+uint32_t TableSelectedRowCount();
+void TableSelectedCells(nsTArray<ProxyAccessible*>* aCellIDs);
+void TableSelectedCellIndices(nsTArray<uint32_t>* aCellIndices);
+void TableSelectedColumnIndices(nsTArray<uint32_t>* aColumnIndices);
+void TableSelectedRowIndices(nsTArray<uint32_t>* aRowIndices);
+void TableSelectColumn(uint32_t aCol);
+void TableSelectRow(uint32_t aRow);
+void TableUnselectColumn(uint32_t aCol);
+void TableUnselectRow(uint32_t aRow);
+bool TableIsProbablyForLayout();
+ProxyAccessible* AtkTableColumnHeader(int32_t aCol);
+ProxyAccessible* AtkTableRowHeader(int32_t aRow);
+
+void SelectedItems(nsTArray<ProxyAccessible*>* aSelectedItems);
+uint32_t SelectedItemCount();
+ProxyAccessible* GetSelectedItem(uint32_t aIndex);
+bool IsItemSelected(uint32_t aIndex);
+bool AddItemToSelection(uint32_t aIndex);
+bool RemoveItemFromSelection(uint32_t aIndex);
+bool SelectAll();
+bool UnselectAll();
+
+void TakeSelection();
+void SetSelected(bool aSelect);
+
+bool DoAction(uint8_t aIndex);
+uint8_t ActionCount();
+void ActionDescriptionAt(uint8_t aIndex, nsString& aDescription);
+void ActionNameAt(uint8_t aIndex, nsString& aName);
+KeyBinding AccessKey();
+KeyBinding KeyboardShortcut();
+void AtkKeyBinding(nsString& aBinding);
+
+double CurValue();
+bool SetCurValue(double aValue);
+double MinValue();
+double MaxValue();
+double Step();
+
+void TakeFocus();
+ProxyAccessible* FocusedChild();
+ProxyAccessible* ChildAtPoint(int32_t aX, int32_t aY,
+ Accessible::EWhichChildAtPoint aWhichChild);
+nsIntRect Bounds();
+
+void Language(nsString& aLocale);
+void DocType(nsString& aType);
+void Title(nsString& aTitle);
+void URL(nsString& aURL);
+void MimeType(nsString aMime);
+void URLDocTypeMimeType(nsString& aURL, nsString& aDocType,
+ nsString& aMimeType);
+
+ProxyAccessible* AccessibleAtPoint(int32_t aX, int32_t aY,
+ bool aNeedsScreenCoords);
+
+void Extents(bool aNeedsScreenCoords, int32_t* aX, int32_t* aY,
+ int32_t* aWidth, int32_t* aHeight);
+
+/**
+ * Return the id of the dom node this accessible represents. Note this
+ * should probably only be used for testing.
+ */
+void DOMNodeID(nsString& aID);
+
+#endif
diff --git a/accessible/ipc/moz.build b/accessible/ipc/moz.build
new file mode 100644
index 000000000..bb0632cd2
--- /dev/null
+++ b/accessible/ipc/moz.build
@@ -0,0 +1,61 @@
+# -*- 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/.
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ DIRS += ['win']
+ LOCAL_INCLUDES += [
+ '/accessible/ipc/win',
+ '/accessible/windows/ia2',
+ '/accessible/windows/msaa',
+ ]
+else:
+ DIRS += ['other']
+ LOCAL_INCLUDES += [
+ '/accessible/ipc/other',
+ ]
+ if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ LOCAL_INCLUDES += [
+ '/accessible/atk',
+ ]
+ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ LOCAL_INCLUDES += [
+ '/accessible/mac',
+ ]
+ else:
+ LOCAL_INCLUDES += [
+ '/accessible/other',
+ ]
+
+EXPORTS.mozilla.a11y += [
+ 'IPCTypes.h',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
+
+if CONFIG['ACCESSIBILITY']:
+ EXPORTS.mozilla.a11y += [
+ 'DocAccessibleChildBase.h',
+ 'DocAccessibleParent.h',
+ 'ProxyAccessibleBase.h',
+ 'ProxyAccessibleShared.h',
+ ]
+
+ UNIFIED_SOURCES += [
+ 'DocAccessibleChildBase.cpp',
+ 'DocAccessibleParent.cpp',
+ 'ProxyAccessibleBase.cpp',
+ ]
+
+ LOCAL_INCLUDES += [
+ '/accessible/base',
+ '/accessible/generic',
+ '/accessible/xpcom',
+ ]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
diff --git a/accessible/ipc/other/DocAccessibleChild.cpp b/accessible/ipc/other/DocAccessibleChild.cpp
new file mode 100644
index 000000000..045b78939
--- /dev/null
+++ b/accessible/ipc/other/DocAccessibleChild.cpp
@@ -0,0 +1,1998 @@
+/* -*- 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 "DocAccessibleChild.h"
+
+#include "Accessible-inl.h"
+#include "ProxyAccessible.h"
+#include "Relation.h"
+#include "HyperTextAccessible-inl.h"
+#include "TextLeafAccessible.h"
+#include "ImageAccessible.h"
+#include "TableAccessible.h"
+#include "TableCellAccessible.h"
+#include "nsIPersistentProperties2.h"
+#include "nsISimpleEnumerator.h"
+#include "nsAccUtils.h"
+#ifdef MOZ_ACCESSIBILITY_ATK
+#include "AccessibleWrap.h"
+#endif
+
+namespace mozilla {
+namespace a11y {
+
+Accessible*
+DocAccessibleChild::IdToAccessible(const uint64_t& aID) const
+{
+ if (!aID)
+ return mDoc;
+
+ if (!mDoc)
+ return nullptr;
+
+ return mDoc->GetAccessibleByUniqueID(reinterpret_cast<void*>(aID));
+}
+
+Accessible*
+DocAccessibleChild::IdToAccessibleLink(const uint64_t& aID) const
+{
+ Accessible* acc = IdToAccessible(aID);
+ return acc && acc->IsLink() ? acc : nullptr;
+}
+
+Accessible*
+DocAccessibleChild::IdToAccessibleSelect(const uint64_t& aID) const
+{
+ Accessible* acc = IdToAccessible(aID);
+ return acc && acc->IsSelect() ? acc : nullptr;
+}
+
+HyperTextAccessible*
+DocAccessibleChild::IdToHyperTextAccessible(const uint64_t& aID) const
+{
+ Accessible* acc = IdToAccessible(aID);
+ return acc && acc->IsHyperText() ? acc->AsHyperText() : nullptr;
+}
+
+TextLeafAccessible*
+DocAccessibleChild::IdToTextLeafAccessible(const uint64_t& aID) const
+{
+ Accessible* acc = IdToAccessible(aID);
+ return acc && acc->IsTextLeaf() ? acc->AsTextLeaf() : nullptr;
+}
+
+ImageAccessible*
+DocAccessibleChild::IdToImageAccessible(const uint64_t& aID) const
+{
+ Accessible* acc = IdToAccessible(aID);
+ return (acc && acc->IsImage()) ? acc->AsImage() : nullptr;
+}
+
+TableCellAccessible*
+DocAccessibleChild::IdToTableCellAccessible(const uint64_t& aID) const
+{
+ Accessible* acc = IdToAccessible(aID);
+ return (acc && acc->IsTableCell()) ? acc->AsTableCell() : nullptr;
+}
+
+TableAccessible*
+DocAccessibleChild::IdToTableAccessible(const uint64_t& aID) const
+{
+ Accessible* acc = IdToAccessible(aID);
+ return (acc && acc->IsTable()) ? acc->AsTable() : nullptr;
+}
+
+bool
+DocAccessibleChild::RecvState(const uint64_t& aID, uint64_t* aState)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (!acc) {
+ *aState = states::DEFUNCT;
+ return true;
+ }
+
+ *aState = acc->State();
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvNativeState(const uint64_t& aID, uint64_t* aState)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (!acc) {
+ *aState = states::DEFUNCT;
+ return true;
+ }
+
+ *aState = acc->NativeState();
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvName(const uint64_t& aID, nsString* aName)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (!acc)
+ return true;
+
+ acc->Name(*aName);
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvValue(const uint64_t& aID, nsString* aValue)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (!acc) {
+ return true;
+ }
+
+ acc->Value(*aValue);
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvHelp(const uint64_t& aID, nsString* aHelp)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (!acc) {
+ return true;
+ }
+
+ acc->Help(*aHelp);
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvDescription(const uint64_t& aID, nsString* aDesc)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (!acc)
+ return true;
+
+ acc->Description(*aDesc);
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvAttributes(const uint64_t& aID, nsTArray<Attribute>* aAttributes)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (!acc)
+ return true;
+
+ nsCOMPtr<nsIPersistentProperties> props = acc->Attributes();
+ return PersistentPropertiesToArray(props, aAttributes);
+}
+
+bool
+DocAccessibleChild::PersistentPropertiesToArray(nsIPersistentProperties* aProps,
+ nsTArray<Attribute>* aAttributes)
+{
+ if (!aProps) {
+ return true;
+ }
+ nsCOMPtr<nsISimpleEnumerator> propEnum;
+ nsresult rv = aProps->Enumerate(getter_AddRefs(propEnum));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(propEnum->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> sup;
+ rv = propEnum->GetNext(getter_AddRefs(sup));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsIPropertyElement> propElem(do_QueryInterface(sup));
+ NS_ENSURE_TRUE(propElem, false);
+
+ nsAutoCString name;
+ rv = propElem->GetKey(name);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsAutoString value;
+ rv = propElem->GetValue(value);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ aAttributes->AppendElement(Attribute(name, value));
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvRelationByType(const uint64_t& aID,
+ const uint32_t& aType,
+ nsTArray<uint64_t>* aTargets)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (!acc)
+ return true;
+
+ auto type = static_cast<RelationType>(aType);
+ Relation rel = acc->RelationByType(type);
+ while (Accessible* target = rel.Next())
+ aTargets->AppendElement(reinterpret_cast<uintptr_t>(target));
+
+ return true;
+}
+
+static void
+AddRelation(Accessible* aAcc, RelationType aType,
+ nsTArray<RelationTargets>* aTargets)
+{
+ Relation rel = aAcc->RelationByType(aType);
+ nsTArray<uint64_t> targets;
+ while (Accessible* target = rel.Next())
+ targets.AppendElement(reinterpret_cast<uintptr_t>(target));
+
+ if (!targets.IsEmpty()) {
+ RelationTargets* newRelation =
+ aTargets->AppendElement(RelationTargets(static_cast<uint32_t>(aType),
+ nsTArray<uint64_t>()));
+ newRelation->Targets().SwapElements(targets);
+ }
+}
+
+bool
+DocAccessibleChild::RecvRelations(const uint64_t& aID,
+ nsTArray<RelationTargets>* aRelations)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (!acc)
+ return true;
+
+#define RELATIONTYPE(gecko, s, a, m, i) AddRelation(acc, RelationType::gecko, aRelations);
+
+#include "RelationTypeMap.h"
+#undef RELATIONTYPE
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvIsSearchbox(const uint64_t& aID, bool* aRetVal)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (!acc)
+ return true;
+
+ *aRetVal = acc->IsSearchbox();
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvLandmarkRole(const uint64_t& aID, nsString* aLandmark)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (!acc) {
+ return true;
+ }
+
+ if (nsIAtom* roleAtom = acc->LandmarkRole()) {
+ roleAtom->ToString(*aLandmark);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvARIARoleAtom(const uint64_t& aID, nsString* aRole)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (!acc) {
+ return true;
+ }
+
+ if (const nsRoleMapEntry* roleMap = acc->ARIARoleMap()) {
+ if (nsIAtom* roleAtom = *(roleMap->roleAtom)) {
+ roleAtom->ToString(*aRole);
+ }
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvGetLevelInternal(const uint64_t& aID, int32_t* aLevel)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (acc) {
+ *aLevel = acc->GetLevelInternal();
+ }
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvScrollTo(const uint64_t& aID,
+ const uint32_t& aScrollType)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (acc) {
+ nsCoreUtils::ScrollTo(acc->Document()->PresShell(), acc->GetContent(),
+ aScrollType);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvScrollToPoint(const uint64_t& aID, const uint32_t& aScrollType, const int32_t& aX, const int32_t& aY)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (acc) {
+ acc->ScrollToPoint(aScrollType, aX, aY);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvCaretLineNumber(const uint64_t& aID, int32_t* aLineNumber)
+{
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ *aLineNumber = acc && acc->IsTextRole() ? acc->CaretLineNumber() : 0;
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvCaretOffset(const uint64_t& aID, int32_t* aOffset)
+{
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ *aOffset = acc && acc->IsTextRole() ? acc->CaretOffset() : 0;
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvSetCaretOffset(const uint64_t& aID,
+ const int32_t& aOffset)
+{
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc && acc->IsTextRole() && acc->IsValidOffset(aOffset)) {
+ acc->SetCaretOffset(aOffset);
+ }
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvCharacterCount(const uint64_t& aID, int32_t* aCount)
+{
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ *aCount = acc ? acc->CharacterCount() : 0;
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvSelectionCount(const uint64_t& aID, int32_t* aCount)
+{
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ *aCount = acc ? acc->SelectionCount() : 0;
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTextSubstring(const uint64_t& aID,
+ const int32_t& aStartOffset,
+ const int32_t& aEndOffset,
+ nsString* aText, bool* aValid)
+{
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (!acc) {
+ return true;
+ }
+
+ *aValid = acc->IsValidRange(aStartOffset, aEndOffset);
+ acc->TextSubstring(aStartOffset, aEndOffset, *aText);
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvGetTextAfterOffset(const uint64_t& aID,
+ const int32_t& aOffset,
+ const int32_t& aBoundaryType,
+ nsString* aText,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset)
+{
+ *aStartOffset = 0;
+ *aEndOffset = 0;
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc) {
+ acc->TextAfterOffset(aOffset, aBoundaryType,
+ aStartOffset, aEndOffset, *aText);
+ }
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvGetTextAtOffset(const uint64_t& aID,
+ const int32_t& aOffset,
+ const int32_t& aBoundaryType,
+ nsString* aText,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset)
+{
+ *aStartOffset = 0;
+ *aEndOffset = 0;
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc) {
+ acc->TextAtOffset(aOffset, aBoundaryType,
+ aStartOffset, aEndOffset, *aText);
+ }
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvGetTextBeforeOffset(const uint64_t& aID,
+ const int32_t& aOffset,
+ const int32_t& aBoundaryType,
+ nsString* aText,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset)
+{
+ *aStartOffset = 0;
+ *aEndOffset = 0;
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc) {
+ acc->TextBeforeOffset(aOffset, aBoundaryType,
+ aStartOffset, aEndOffset, *aText);
+ }
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvCharAt(const uint64_t& aID,
+ const int32_t& aOffset,
+ uint16_t* aChar)
+{
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ *aChar = acc && acc->IsTextRole() ?
+ static_cast<uint16_t>(acc->CharAt(aOffset)) : 0;
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTextAttributes(const uint64_t& aID,
+ const bool& aIncludeDefAttrs,
+ const int32_t& aOffset,
+ nsTArray<Attribute>* aAttributes,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset)
+{
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (!acc || !acc->IsTextRole()) {
+ return true;
+ }
+
+ nsCOMPtr<nsIPersistentProperties> props =
+ acc->TextAttributes(aIncludeDefAttrs, aOffset, aStartOffset, aEndOffset);
+ return PersistentPropertiesToArray(props, aAttributes);
+}
+
+bool
+DocAccessibleChild::RecvDefaultTextAttributes(const uint64_t& aID,
+ nsTArray<Attribute> *aAttributes)
+{
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (!acc || !acc->IsTextRole()) {
+ return true;
+ }
+
+ nsCOMPtr<nsIPersistentProperties> props = acc->DefaultTextAttributes();
+ return PersistentPropertiesToArray(props, aAttributes);
+}
+
+bool
+DocAccessibleChild::RecvTextBounds(const uint64_t& aID,
+ const int32_t& aStartOffset,
+ const int32_t& aEndOffset,
+ const uint32_t& aCoordType,
+ nsIntRect* aRetVal)
+{
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc && acc->IsTextRole()) {
+ *aRetVal = acc->TextBounds(aStartOffset, aEndOffset, aCoordType);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvCharBounds(const uint64_t& aID,
+ const int32_t& aOffset,
+ const uint32_t& aCoordType,
+ nsIntRect* aRetVal)
+{
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc && acc->IsTextRole()) {
+ *aRetVal = acc->CharBounds(aOffset, aCoordType);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvOffsetAtPoint(const uint64_t& aID,
+ const int32_t& aX,
+ const int32_t& aY,
+ const uint32_t& aCoordType,
+ int32_t* aRetVal)
+{
+ *aRetVal = -1;
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc && acc->IsTextRole()) {
+ *aRetVal = acc->OffsetAtPoint(aX, aY, aCoordType);
+ }
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvSelectionBoundsAt(const uint64_t& aID,
+ const int32_t& aSelectionNum,
+ bool* aSucceeded,
+ nsString* aData,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset)
+{
+ *aSucceeded = false;
+ *aStartOffset = 0;
+ *aEndOffset = 0;
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc && acc->IsTextRole()) {
+ *aSucceeded =
+ acc->SelectionBoundsAt(aSelectionNum, aStartOffset, aEndOffset);
+ if (*aSucceeded) {
+ acc->TextSubstring(*aStartOffset, *aEndOffset, *aData);
+ }
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvSetSelectionBoundsAt(const uint64_t& aID,
+ const int32_t& aSelectionNum,
+ const int32_t& aStartOffset,
+ const int32_t& aEndOffset,
+ bool* aSucceeded)
+{
+ *aSucceeded = false;
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc && acc->IsTextRole()) {
+ *aSucceeded =
+ acc->SetSelectionBoundsAt(aSelectionNum, aStartOffset, aEndOffset);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvAddToSelection(const uint64_t& aID,
+ const int32_t& aStartOffset,
+ const int32_t& aEndOffset,
+ bool* aSucceeded)
+{
+ *aSucceeded = false;
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc && acc->IsTextRole()) {
+ *aSucceeded = acc->AddToSelection(aStartOffset, aEndOffset);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvRemoveFromSelection(const uint64_t& aID,
+ const int32_t& aSelectionNum,
+ bool* aSucceeded)
+{
+ *aSucceeded = false;
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc && acc->IsTextRole()) {
+ *aSucceeded = acc->RemoveFromSelection(aSelectionNum);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvScrollSubstringTo(const uint64_t& aID,
+ const int32_t& aStartOffset,
+ const int32_t& aEndOffset,
+ const uint32_t& aScrollType)
+{
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc) {
+ acc->ScrollSubstringTo(aStartOffset, aEndOffset, aScrollType);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvScrollSubstringToPoint(const uint64_t& aID,
+ const int32_t& aStartOffset,
+ const int32_t& aEndOffset,
+ const uint32_t& aCoordinateType,
+ const int32_t& aX,
+ const int32_t& aY)
+{
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc) {
+ acc->ScrollSubstringToPoint(aStartOffset, aEndOffset, aCoordinateType,
+ aX, aY);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvText(const uint64_t& aID,
+ nsString* aText)
+{
+ TextLeafAccessible* acc = IdToTextLeafAccessible(aID);
+ if (acc) {
+ *aText = acc->Text();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvReplaceText(const uint64_t& aID,
+ const nsString& aText)
+{
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc && acc->IsTextRole()) {
+ acc->ReplaceText(aText);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvInsertText(const uint64_t& aID,
+ const nsString& aText,
+ const int32_t& aPosition, bool* aValid)
+{
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc && acc->IsTextRole()) {
+ *aValid = acc->IsValidOffset(aPosition);
+ acc->InsertText(aText, aPosition);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvCopyText(const uint64_t& aID,
+ const int32_t& aStartPos,
+ const int32_t& aEndPos, bool* aValid)
+{
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc && acc->IsTextRole()) {
+ acc->CopyText(aStartPos, aEndPos);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvCutText(const uint64_t& aID,
+ const int32_t& aStartPos,
+ const int32_t& aEndPos, bool* aValid)
+{
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc && acc->IsTextRole()) {
+ *aValid = acc->IsValidRange(aStartPos, aEndPos);
+ acc->CutText(aStartPos, aEndPos);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvDeleteText(const uint64_t& aID,
+ const int32_t& aStartPos,
+ const int32_t& aEndPos, bool* aValid)
+{
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc && acc->IsTextRole()) {
+ *aValid = acc->IsValidRange(aStartPos, aEndPos);
+ acc->DeleteText(aStartPos, aEndPos);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvPasteText(const uint64_t& aID,
+ const int32_t& aPosition, bool* aValid)
+{
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc && acc->IsTextRole()) {
+ *aValid = acc->IsValidOffset(aPosition);
+ acc->PasteText(aPosition);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvImagePosition(const uint64_t& aID,
+ const uint32_t& aCoordType,
+ nsIntPoint* aRetVal)
+{
+ ImageAccessible* acc = IdToImageAccessible(aID);
+ if (acc) {
+ *aRetVal = acc->Position(aCoordType);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvImageSize(const uint64_t& aID,
+ nsIntSize* aRetVal)
+{
+
+ ImageAccessible* acc = IdToImageAccessible(aID);
+ if (acc) {
+ *aRetVal = acc->Size();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvStartOffset(const uint64_t& aID,
+ uint32_t* aRetVal,
+ bool* aOk)
+{
+ Accessible* acc = IdToAccessibleLink(aID);
+ if (acc) {
+ *aRetVal = acc->StartOffset();
+ *aOk = true;
+ } else {
+ *aRetVal = 0;
+ *aOk = false;
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvEndOffset(const uint64_t& aID,
+ uint32_t* aRetVal,
+ bool* aOk)
+{
+ Accessible* acc = IdToAccessibleLink(aID);
+ if (acc) {
+ *aRetVal = acc->EndOffset();
+ *aOk = true;
+ } else {
+ *aRetVal = 0;
+ *aOk = false;
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvIsLinkValid(const uint64_t& aID,
+ bool* aRetVal)
+{
+ Accessible* acc = IdToAccessibleLink(aID);
+ if (acc) {
+ *aRetVal = acc->IsLinkValid();
+ } else {
+ *aRetVal = false;
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvAnchorCount(const uint64_t& aID,
+ uint32_t* aRetVal,
+ bool* aOk)
+{
+ Accessible* acc = IdToAccessibleLink(aID);
+ if (acc) {
+ *aRetVal = acc->AnchorCount();
+ *aOk = true;
+ } else {
+ *aRetVal = 0;
+ *aOk = false;
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvAnchorURIAt(const uint64_t& aID,
+ const uint32_t& aIndex,
+ nsCString* aURI,
+ bool* aOk)
+{
+ Accessible* acc = IdToAccessibleLink(aID);
+ *aOk = false;
+ if (acc) {
+ nsCOMPtr<nsIURI> uri = acc->AnchorURIAt(aIndex);
+ if (uri) {
+ uri->GetSpec(*aURI);
+ *aOk = true;
+ }
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvAnchorAt(const uint64_t& aID,
+ const uint32_t& aIndex,
+ uint64_t* aIDOfAnchor,
+ bool* aOk)
+{
+ *aIDOfAnchor = 0;
+ *aOk = false;
+ Accessible* acc = IdToAccessibleLink(aID);
+ if (acc) {
+ Accessible* anchor = acc->AnchorAt(aIndex);
+ if (anchor) {
+ *aIDOfAnchor = reinterpret_cast<uint64_t>(anchor->UniqueID());
+ *aOk = true;
+ }
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvLinkCount(const uint64_t& aID,
+ uint32_t* aCount)
+{
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ *aCount = acc ? acc->LinkCount() : 0;
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvLinkAt(const uint64_t& aID,
+ const uint32_t& aIndex,
+ uint64_t* aIDOfLink,
+ bool* aOk)
+{
+ *aIDOfLink = 0;
+ *aOk = false;
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc) {
+ Accessible* link = acc->LinkAt(aIndex);
+ if (link) {
+ *aIDOfLink = reinterpret_cast<uint64_t>(link->UniqueID());
+ *aOk = true;
+ }
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvLinkIndexOf(const uint64_t& aID,
+ const uint64_t& aLinkID,
+ int32_t* aIndex)
+{
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ Accessible* link = IdToAccessible(aLinkID);
+ *aIndex = -1;
+ if (acc && link) {
+ *aIndex = acc->LinkIndexOf(link);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvLinkIndexAtOffset(const uint64_t& aID,
+ const uint32_t& aOffset,
+ int32_t* aIndex)
+{
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ *aIndex = acc ? acc->LinkIndexAtOffset(aOffset) : -1;
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableOfACell(const uint64_t& aID,
+ uint64_t* aTableID,
+ bool* aOk)
+{
+ *aTableID = 0;
+ *aOk = false;
+ TableCellAccessible* acc = IdToTableCellAccessible(aID);
+ if (acc) {
+ TableAccessible* table = acc->Table();
+ if (table) {
+ *aTableID = reinterpret_cast<uint64_t>(table->AsAccessible()->UniqueID());
+ *aOk = true;
+ }
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvColIdx(const uint64_t& aID,
+ uint32_t* aIndex)
+{
+ *aIndex = 0;
+ TableCellAccessible* acc = IdToTableCellAccessible(aID);
+ if (acc) {
+ *aIndex = acc->ColIdx();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvRowIdx(const uint64_t& aID,
+ uint32_t* aIndex)
+{
+ *aIndex = 0;
+ TableCellAccessible* acc = IdToTableCellAccessible(aID);
+ if (acc) {
+ *aIndex = acc->RowIdx();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvGetPosition(const uint64_t& aID,
+ uint32_t* aColIdx, uint32_t* aRowIdx)
+{
+ *aColIdx = 0;
+ *aRowIdx = 0;
+ TableCellAccessible* acc = IdToTableCellAccessible(aID);
+ if (acc) {
+ *aColIdx = acc->ColIdx();
+ *aRowIdx = acc->RowIdx();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvGetColRowExtents(const uint64_t& aID,
+ uint32_t* aColIdx, uint32_t* aRowIdx,
+ uint32_t* aColExtent, uint32_t* aRowExtent)
+{
+ *aColIdx = 0;
+ *aRowIdx = 0;
+ *aColExtent = 0;
+ *aRowExtent = 0;
+ TableCellAccessible* acc = IdToTableCellAccessible(aID);
+ if (acc) {
+ *aColIdx = acc->ColIdx();
+ *aRowIdx = acc->RowIdx();
+ *aColExtent = acc->ColExtent();
+ *aRowExtent = acc->RowExtent();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvColExtent(const uint64_t& aID,
+ uint32_t* aExtent)
+{
+ *aExtent = 0;
+ TableCellAccessible* acc = IdToTableCellAccessible(aID);
+ if (acc) {
+ *aExtent = acc->ColExtent();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvRowExtent(const uint64_t& aID,
+ uint32_t* aExtent)
+{
+ *aExtent = 0;
+ TableCellAccessible* acc = IdToTableCellAccessible(aID);
+ if (acc) {
+ *aExtent = acc->RowExtent();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvColHeaderCells(const uint64_t& aID,
+ nsTArray<uint64_t>* aCells)
+{
+ TableCellAccessible* acc = IdToTableCellAccessible(aID);
+ if (acc) {
+ AutoTArray<Accessible*, 10> headerCells;
+ acc->ColHeaderCells(&headerCells);
+ aCells->SetCapacity(headerCells.Length());
+ for (uint32_t i = 0; i < headerCells.Length(); ++i) {
+ aCells->AppendElement(
+ reinterpret_cast<uint64_t>(headerCells[i]->UniqueID()));
+ }
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvRowHeaderCells(const uint64_t& aID,
+ nsTArray<uint64_t>* aCells)
+{
+ TableCellAccessible* acc = IdToTableCellAccessible(aID);
+ if (acc) {
+ AutoTArray<Accessible*, 10> headerCells;
+ acc->RowHeaderCells(&headerCells);
+ aCells->SetCapacity(headerCells.Length());
+ for (uint32_t i = 0; i < headerCells.Length(); ++i) {
+ aCells->AppendElement(
+ reinterpret_cast<uint64_t>(headerCells[i]->UniqueID()));
+ }
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvIsCellSelected(const uint64_t& aID,
+ bool* aSelected)
+{
+ TableCellAccessible* acc = IdToTableCellAccessible(aID);
+ *aSelected = acc && acc->Selected();
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableCaption(const uint64_t& aID,
+ uint64_t* aCaptionID,
+ bool* aOk)
+{
+ *aCaptionID = 0;
+ *aOk = false;
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ Accessible* caption = acc->Caption();
+ if (caption) {
+ *aCaptionID = reinterpret_cast<uint64_t>(caption->UniqueID());
+ *aOk = true;
+ }
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableSummary(const uint64_t& aID,
+ nsString* aSummary)
+{
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ acc->Summary(*aSummary);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableColumnCount(const uint64_t& aID,
+ uint32_t* aColCount)
+{
+ *aColCount = 0;
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ *aColCount = acc->ColCount();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableRowCount(const uint64_t& aID,
+ uint32_t* aRowCount)
+{
+ *aRowCount = 0;
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ *aRowCount = acc->RowCount();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableCellAt(const uint64_t& aID,
+ const uint32_t& aRow,
+ const uint32_t& aCol,
+ uint64_t* aCellID,
+ bool* aOk)
+{
+ *aCellID = 0;
+ *aOk = false;
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ Accessible* cell = acc->CellAt(aRow, aCol);
+ if (cell) {
+ *aCellID = reinterpret_cast<uint64_t>(cell->UniqueID());
+ *aOk = true;
+ }
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableCellIndexAt(const uint64_t& aID,
+ const uint32_t& aRow,
+ const uint32_t& aCol,
+ int32_t* aIndex)
+{
+ *aIndex = -1;
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ *aIndex = acc->CellIndexAt(aRow, aCol);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableColumnIndexAt(const uint64_t& aID,
+ const uint32_t& aCellIndex,
+ int32_t* aCol)
+{
+ *aCol = -1;
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ *aCol = acc->ColIndexAt(aCellIndex);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableRowIndexAt(const uint64_t& aID,
+ const uint32_t& aCellIndex,
+ int32_t* aRow)
+{
+ *aRow = -1;
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ *aRow = acc->RowIndexAt(aCellIndex);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableRowAndColumnIndicesAt(const uint64_t& aID,
+ const uint32_t& aCellIndex,
+ int32_t* aRow,
+ int32_t* aCol)
+{
+ *aRow = -1;
+ *aCol = -1;
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ acc->RowAndColIndicesAt(aCellIndex, aRow, aCol);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableColumnExtentAt(const uint64_t& aID,
+ const uint32_t& aRow,
+ const uint32_t& aCol,
+ uint32_t* aExtent)
+{
+ *aExtent = 0;
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ *aExtent = acc->ColExtentAt(aRow, aCol);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableRowExtentAt(const uint64_t& aID,
+ const uint32_t& aRow,
+ const uint32_t& aCol,
+ uint32_t* aExtent)
+{
+ *aExtent = 0;
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ *aExtent = acc->RowExtentAt(aRow, aCol);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableColumnDescription(const uint64_t& aID,
+ const uint32_t& aCol,
+ nsString* aDescription)
+{
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ acc->ColDescription(aCol, *aDescription);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableRowDescription(const uint64_t& aID,
+ const uint32_t& aRow,
+ nsString* aDescription)
+{
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ acc->RowDescription(aRow, *aDescription);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableColumnSelected(const uint64_t& aID,
+ const uint32_t& aCol,
+ bool* aSelected)
+{
+ *aSelected = false;
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ *aSelected = acc->IsColSelected(aCol);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableRowSelected(const uint64_t& aID,
+ const uint32_t& aRow,
+ bool* aSelected)
+{
+ *aSelected = false;
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ *aSelected = acc->IsRowSelected(aRow);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableCellSelected(const uint64_t& aID,
+ const uint32_t& aRow,
+ const uint32_t& aCol,
+ bool* aSelected)
+{
+ *aSelected = false;
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ *aSelected = acc->IsCellSelected(aRow, aCol);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableSelectedCellCount(const uint64_t& aID,
+ uint32_t* aSelectedCells)
+{
+ *aSelectedCells = 0;
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ *aSelectedCells = acc->SelectedCellCount();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableSelectedColumnCount(const uint64_t& aID,
+ uint32_t* aSelectedColumns)
+{
+ *aSelectedColumns = 0;
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ *aSelectedColumns = acc->SelectedColCount();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableSelectedRowCount(const uint64_t& aID,
+ uint32_t* aSelectedRows)
+{
+ *aSelectedRows = 0;
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ *aSelectedRows = acc->SelectedRowCount();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableSelectedCells(const uint64_t& aID,
+ nsTArray<uint64_t>* aCellIDs)
+{
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ AutoTArray<Accessible*, 30> cells;
+ acc->SelectedCells(&cells);
+ aCellIDs->SetCapacity(cells.Length());
+ for (uint32_t i = 0; i < cells.Length(); ++i) {
+ aCellIDs->AppendElement(
+ reinterpret_cast<uint64_t>(cells[i]->UniqueID()));
+ }
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableSelectedCellIndices(const uint64_t& aID,
+ nsTArray<uint32_t>* aCellIndices)
+{
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ acc->SelectedCellIndices(aCellIndices);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableSelectedColumnIndices(const uint64_t& aID,
+ nsTArray<uint32_t>* aColumnIndices)
+{
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ acc->SelectedColIndices(aColumnIndices);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableSelectedRowIndices(const uint64_t& aID,
+ nsTArray<uint32_t>* aRowIndices)
+{
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ acc->SelectedRowIndices(aRowIndices);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableSelectColumn(const uint64_t& aID,
+ const uint32_t& aCol)
+{
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ acc->SelectCol(aCol);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableSelectRow(const uint64_t& aID,
+ const uint32_t& aRow)
+{
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ acc->SelectRow(aRow);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableUnselectColumn(const uint64_t& aID,
+ const uint32_t& aCol)
+{
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ acc->UnselectCol(aCol);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableUnselectRow(const uint64_t& aID,
+ const uint32_t& aRow)
+{
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ acc->UnselectRow(aRow);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTableIsProbablyForLayout(const uint64_t& aID,
+ bool* aForLayout)
+{
+ *aForLayout = false;
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ *aForLayout = acc->IsProbablyLayoutTable();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvAtkTableColumnHeader(const uint64_t& aID,
+ const int32_t& aCol,
+ uint64_t* aHeader,
+ bool* aOk)
+{
+ *aHeader = 0;
+ *aOk = false;
+
+#ifdef MOZ_ACCESSIBILITY_ATK
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ Accessible* header = AccessibleWrap::GetColumnHeader(acc, aCol);
+ if (header) {
+ *aHeader = reinterpret_cast<uint64_t>(header->UniqueID());
+ *aOk = true;
+ }
+ }
+#endif
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvAtkTableRowHeader(const uint64_t& aID,
+ const int32_t& aRow,
+ uint64_t* aHeader,
+ bool* aOk)
+{
+ *aHeader = 0;
+ *aOk = false;
+
+#ifdef MOZ_ACCESSIBILITY_ATK
+ TableAccessible* acc = IdToTableAccessible(aID);
+ if (acc) {
+ Accessible* header = AccessibleWrap::GetRowHeader(acc, aRow);
+ if (header) {
+ *aHeader = reinterpret_cast<uint64_t>(header->UniqueID());
+ *aOk = true;
+ }
+ }
+#endif
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvSelectedItems(const uint64_t& aID,
+ nsTArray<uint64_t>* aSelectedItemIDs)
+{
+ Accessible* acc = IdToAccessibleSelect(aID);
+ if (acc) {
+ AutoTArray<Accessible*, 10> selectedItems;
+ acc->SelectedItems(&selectedItems);
+ aSelectedItemIDs->SetCapacity(selectedItems.Length());
+ for (size_t i = 0; i < selectedItems.Length(); ++i) {
+ aSelectedItemIDs->AppendElement(
+ reinterpret_cast<uint64_t>(selectedItems[i]->UniqueID()));
+ }
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvSelectedItemCount(const uint64_t& aID,
+ uint32_t* aCount)
+{
+ *aCount = 0;
+ Accessible* acc = IdToAccessibleSelect(aID);
+ if (acc) {
+ *aCount = acc->SelectedItemCount();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvGetSelectedItem(const uint64_t& aID,
+ const uint32_t& aIndex,
+ uint64_t* aSelected,
+ bool* aOk)
+{
+ *aSelected = 0;
+ *aOk = false;
+ Accessible* acc = IdToAccessibleSelect(aID);
+ if (acc) {
+ Accessible* item = acc->GetSelectedItem(aIndex);
+ if (item) {
+ *aSelected = reinterpret_cast<uint64_t>(item->UniqueID());
+ *aOk = true;
+ }
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvIsItemSelected(const uint64_t& aID,
+ const uint32_t& aIndex,
+ bool* aSelected)
+{
+ *aSelected = false;
+ Accessible* acc = IdToAccessibleSelect(aID);
+ if (acc) {
+ *aSelected = acc->IsItemSelected(aIndex);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvAddItemToSelection(const uint64_t& aID,
+ const uint32_t& aIndex,
+ bool* aSuccess)
+{
+ *aSuccess = false;
+ Accessible* acc = IdToAccessibleSelect(aID);
+ if (acc) {
+ *aSuccess = acc->AddItemToSelection(aIndex);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvRemoveItemFromSelection(const uint64_t& aID,
+ const uint32_t& aIndex,
+ bool* aSuccess)
+{
+ *aSuccess = false;
+ Accessible* acc = IdToAccessibleSelect(aID);
+ if (acc) {
+ *aSuccess = acc->RemoveItemFromSelection(aIndex);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvSelectAll(const uint64_t& aID,
+ bool* aSuccess)
+{
+ *aSuccess = false;
+ Accessible* acc = IdToAccessibleSelect(aID);
+ if (acc) {
+ *aSuccess = acc->SelectAll();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvUnselectAll(const uint64_t& aID,
+ bool* aSuccess)
+{
+ *aSuccess = false;
+ Accessible* acc = IdToAccessibleSelect(aID);
+ if (acc) {
+ *aSuccess = acc->UnselectAll();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTakeSelection(const uint64_t& aID)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (acc) {
+ acc->TakeSelection();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvSetSelected(const uint64_t& aID, const bool& aSelect)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (acc) {
+ acc->SetSelected(aSelect);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvDoAction(const uint64_t& aID,
+ const uint8_t& aIndex,
+ bool* aSuccess)
+{
+ *aSuccess = false;
+ Accessible* acc = IdToAccessible(aID);
+ if (acc) {
+ *aSuccess = acc->DoAction(aIndex);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvActionCount(const uint64_t& aID,
+ uint8_t* aCount)
+{
+ *aCount = 0;
+ Accessible* acc = IdToAccessible(aID);
+ if (acc) {
+ *aCount = acc->ActionCount();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvActionDescriptionAt(const uint64_t& aID,
+ const uint8_t& aIndex,
+ nsString* aDescription)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (acc) {
+ acc->ActionDescriptionAt(aIndex, *aDescription);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvActionNameAt(const uint64_t& aID,
+ const uint8_t& aIndex,
+ nsString* aName)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (acc) {
+ acc->ActionNameAt(aIndex, *aName);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvAccessKey(const uint64_t& aID,
+ uint32_t* aKey,
+ uint32_t* aModifierMask)
+{
+ *aKey = 0;
+ *aModifierMask = 0;
+ Accessible* acc = IdToAccessible(aID);
+ if (acc) {
+ KeyBinding kb = acc->AccessKey();
+ *aKey = kb.Key();
+ *aModifierMask = kb.ModifierMask();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvKeyboardShortcut(const uint64_t& aID,
+ uint32_t* aKey,
+ uint32_t* aModifierMask)
+{
+ *aKey = 0;
+ *aModifierMask = 0;
+ Accessible* acc = IdToAccessible(aID);
+ if (acc) {
+ KeyBinding kb = acc->KeyboardShortcut();
+ *aKey = kb.Key();
+ *aModifierMask = kb.ModifierMask();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvAtkKeyBinding(const uint64_t& aID,
+ nsString* aResult)
+{
+#ifdef MOZ_ACCESSIBILITY_ATK
+ Accessible* acc = IdToAccessible(aID);
+ if (acc) {
+ AccessibleWrap::GetKeyBinding(acc, *aResult);
+ }
+#endif
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvCurValue(const uint64_t& aID,
+ double* aValue)
+{
+ *aValue = UnspecifiedNaN<double>();
+ Accessible* acc = IdToAccessible(aID);
+ if (acc) {
+ *aValue = acc->CurValue();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvSetCurValue(const uint64_t& aID,
+ const double& aValue,
+ bool* aRetVal)
+{
+ *aRetVal = false;
+ Accessible* acc = IdToAccessible(aID);
+ if (acc) {
+ *aRetVal = acc->SetCurValue(aValue);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvMinValue(const uint64_t& aID,
+ double* aValue)
+{
+ *aValue = UnspecifiedNaN<double>();
+ Accessible* acc = IdToAccessible(aID);
+ if (acc) {
+ *aValue = acc->MinValue();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvMaxValue(const uint64_t& aID,
+ double* aValue)
+{
+ *aValue = UnspecifiedNaN<double>();
+ Accessible* acc = IdToAccessible(aID);
+ if (acc) {
+ *aValue = acc->MaxValue();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvStep(const uint64_t& aID,
+ double* aStep)
+{
+ *aStep = UnspecifiedNaN<double>();
+ Accessible* acc = IdToAccessible(aID);
+ if (acc) {
+ *aStep = acc->Step();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTakeFocus(const uint64_t& aID)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (acc) {
+ acc->TakeFocus();
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvFocusedChild(const uint64_t& aID,
+ uint64_t* aChild,
+ bool* aOk)
+{
+ *aChild = 0;
+ *aOk = false;
+ Accessible* acc = IdToAccessible(aID);
+ if (acc) {
+ Accessible* child = acc->FocusedChild();
+ if (child) {
+ *aChild = reinterpret_cast<uint64_t>(child->UniqueID());
+ *aOk = true;
+ }
+ }
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvLanguage(const uint64_t& aID,
+ nsString* aLocale)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (acc) {
+ acc->Language(*aLocale);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvDocType(const uint64_t& aID,
+ nsString* aType)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (acc && acc->IsDoc()) {
+ acc->AsDoc()->DocType(*aType);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvTitle(const uint64_t& aID,
+ nsString* aTitle)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (acc) {
+ mozilla::ErrorResult rv;
+ acc->GetContent()->GetTextContent(*aTitle, rv);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvURL(const uint64_t& aID,
+ nsString* aURL)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (acc && acc->IsDoc()) {
+ acc->AsDoc()->URL(*aURL);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvMimeType(const uint64_t& aID,
+ nsString* aMime)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (acc && acc->IsDoc()) {
+ acc->AsDoc()->MimeType(*aMime);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvURLDocTypeMimeType(const uint64_t& aID,
+ nsString* aURL,
+ nsString* aDocType,
+ nsString* aMimeType)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (acc && acc->IsDoc()) {
+ DocAccessible* doc = acc->AsDoc();
+ doc->URL(*aURL);
+ doc->DocType(*aDocType);
+ doc->MimeType(*aMimeType);
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvAccessibleAtPoint(const uint64_t& aID,
+ const int32_t& aX,
+ const int32_t& aY,
+ const bool& aNeedsScreenCoords,
+ const uint32_t& aWhich,
+ uint64_t* aResult,
+ bool* aOk)
+{
+ *aResult = 0;
+ *aOk = false;
+ Accessible* acc = IdToAccessible(aID);
+ if (acc && !acc->IsDefunct() && !nsAccUtils::MustPrune(acc)) {
+ int32_t x = aX;
+ int32_t y = aY;
+ if (aNeedsScreenCoords) {
+ nsIntPoint winCoords =
+ nsCoreUtils::GetScreenCoordsForWindow(acc->GetNode());
+ x += winCoords.x;
+ y += winCoords.y;
+ }
+
+ Accessible* result =
+ acc->ChildAtPoint(x, y,
+ static_cast<Accessible::EWhichChildAtPoint>(aWhich));
+ if (result) {
+ *aResult = reinterpret_cast<uint64_t>(result->UniqueID());
+ *aOk = true;
+ }
+ }
+
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvExtents(const uint64_t& aID,
+ const bool& aNeedsScreenCoords,
+ int32_t* aX,
+ int32_t* aY,
+ int32_t* aWidth,
+ int32_t* aHeight)
+{
+ *aX = 0;
+ *aY = 0;
+ *aWidth = 0;
+ *aHeight = 0;
+ Accessible* acc = IdToAccessible(aID);
+ if (acc && !acc->IsDefunct()) {
+ nsIntRect screenRect = acc->Bounds();
+ if (!screenRect.IsEmpty()) {
+ if (aNeedsScreenCoords) {
+ nsIntPoint winCoords =
+ nsCoreUtils::GetScreenCoordsForWindow(acc->GetNode());
+ screenRect.x -= winCoords.x;
+ screenRect.y -= winCoords.y;
+ }
+
+ *aX = screenRect.x;
+ *aY = screenRect.y;
+ *aWidth = screenRect.width;
+ *aHeight = screenRect.height;
+ }
+ }
+ return true;
+}
+
+bool
+DocAccessibleChild::RecvDOMNodeID(const uint64_t& aID, nsString* aDOMNodeID)
+{
+ Accessible* acc = IdToAccessible(aID);
+ if (!acc) {
+ return true;
+ }
+
+ nsIContent* content = acc->GetContent();
+ if (!content) {
+ return true;
+ }
+
+ nsIAtom* id = content->GetID();
+ if (id) {
+ id->ToString(*aDOMNodeID);
+ }
+
+ return true;
+}
+
+}
+}
diff --git a/accessible/ipc/other/DocAccessibleChild.h b/accessible/ipc/other/DocAccessibleChild.h
new file mode 100644
index 000000000..7c9d61da7
--- /dev/null
+++ b/accessible/ipc/other/DocAccessibleChild.h
@@ -0,0 +1,489 @@
+/* -*- 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_DocAccessibleChild_h
+#define mozilla_a11y_DocAccessibleChild_h
+
+#include "mozilla/a11y/DocAccessibleChildBase.h"
+
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+class HyperTextAccessible;
+class TextLeafAccessible;
+class ImageAccessible;
+class TableAccessible;
+class TableCellAccessible;
+
+/*
+ * These objects handle content side communication for an accessible document,
+ * and their lifetime is the same as the document they represent.
+ */
+class DocAccessibleChild : public DocAccessibleChildBase
+{
+public:
+ explicit DocAccessibleChild(DocAccessible* aDoc)
+ : DocAccessibleChildBase(aDoc)
+ {
+ MOZ_COUNT_CTOR_INHERITED(DocAccessibleChild, DocAccessibleChildBase);
+ }
+
+ ~DocAccessibleChild()
+ {
+ MOZ_COUNT_DTOR_INHERITED(DocAccessibleChild, DocAccessibleChildBase);
+ }
+
+ /*
+ * Return the state for the accessible with given ID.
+ */
+ virtual bool RecvState(const uint64_t& aID, uint64_t* aState) override;
+
+ /*
+ * Return the native state for the accessible with given ID.
+ */
+ virtual bool RecvNativeState(const uint64_t& aID, uint64_t* aState) override;
+
+ /*
+ * Get the name for the accessible with given id.
+ */
+ virtual bool RecvName(const uint64_t& aID, nsString* aName) override;
+
+ virtual bool RecvValue(const uint64_t& aID, nsString* aValue) override;
+
+ virtual bool RecvHelp(const uint64_t& aID, nsString* aHelp) override;
+
+ /*
+ * Get the description for the accessible with given id.
+ */
+ virtual bool RecvDescription(const uint64_t& aID, nsString* aDesc) override;
+ virtual bool RecvRelationByType(const uint64_t& aID, const uint32_t& aType,
+ nsTArray<uint64_t>* aTargets) override;
+ virtual bool RecvRelations(const uint64_t& aID,
+ nsTArray<RelationTargets>* aRelations)
+ override;
+
+ virtual bool RecvIsSearchbox(const uint64_t& aID, bool* aRetVal) override;
+
+ virtual bool RecvLandmarkRole(const uint64_t& aID, nsString* aLandmark) override;
+
+ virtual bool RecvARIARoleAtom(const uint64_t& aID, nsString* aRole) override;
+
+ virtual bool RecvGetLevelInternal(const uint64_t& aID, int32_t* aLevel) override;
+
+ virtual bool RecvAttributes(const uint64_t& aID,
+ nsTArray<Attribute> *aAttributes) override;
+ virtual bool RecvScrollTo(const uint64_t& aID, const uint32_t& aScrollType)
+ override;
+ virtual bool RecvScrollToPoint(const uint64_t& aID,
+ const uint32_t& aScrollType,
+ const int32_t& aX, const int32_t& aY) override;
+
+ virtual bool RecvCaretLineNumber(const uint64_t& aID, int32_t* aLineNumber)
+ override;
+ virtual bool RecvCaretOffset(const uint64_t& aID, int32_t* aOffset)
+ override;
+ virtual bool RecvSetCaretOffset(const uint64_t& aID, const int32_t& aOffset)
+ override;
+
+ virtual bool RecvCharacterCount(const uint64_t& aID, int32_t* aCount)
+ override;
+ virtual bool RecvSelectionCount(const uint64_t& aID, int32_t* aCount)
+ override;
+
+ virtual bool RecvTextSubstring(const uint64_t& aID,
+ const int32_t& aStartOffset,
+ const int32_t& aEndOffset, nsString* aText,
+ bool* aValid) override;
+
+ virtual bool RecvGetTextAfterOffset(const uint64_t& aID,
+ const int32_t& aOffset,
+ const int32_t& aBoundaryType,
+ nsString* aText, int32_t* aStartOffset,
+ int32_t* aEndOffset) override;
+ virtual bool RecvGetTextAtOffset(const uint64_t& aID,
+ const int32_t& aOffset,
+ const int32_t& aBoundaryType,
+ nsString* aText, int32_t* aStartOffset,
+ int32_t* aEndOffset) override;
+ virtual bool RecvGetTextBeforeOffset(const uint64_t& aID,
+ const int32_t& aOffset,
+ const int32_t& aBoundaryType,
+ nsString* aText, int32_t* aStartOffset,
+ int32_t* aEndOffset) override;
+
+ virtual bool RecvCharAt(const uint64_t& aID,
+ const int32_t& aOffset,
+ uint16_t* aChar) override;
+
+ virtual bool RecvTextAttributes(const uint64_t& aID,
+ const bool& aIncludeDefAttrs,
+ const int32_t& aOffset,
+ nsTArray<Attribute>* aAttributes,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset)
+ override;
+
+ virtual bool RecvDefaultTextAttributes(const uint64_t& aID,
+ nsTArray<Attribute>* aAttributes)
+ override;
+
+ virtual bool RecvTextBounds(const uint64_t& aID,
+ const int32_t& aStartOffset,
+ const int32_t& aEndOffset,
+ const uint32_t& aCoordType,
+ nsIntRect* aRetVal) override;
+
+ virtual bool RecvCharBounds(const uint64_t& aID,
+ const int32_t& aOffset,
+ const uint32_t& aCoordType,
+ nsIntRect* aRetVal) override;
+
+ virtual bool RecvOffsetAtPoint(const uint64_t& aID,
+ const int32_t& aX,
+ const int32_t& aY,
+ const uint32_t& aCoordType,
+ int32_t* aRetVal) override;
+
+ virtual bool RecvSelectionBoundsAt(const uint64_t& aID,
+ const int32_t& aSelectionNum,
+ bool* aSucceeded,
+ nsString* aData,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset) override;
+
+ virtual bool RecvSetSelectionBoundsAt(const uint64_t& aID,
+ const int32_t& aSelectionNum,
+ const int32_t& aStartOffset,
+ const int32_t& aEndOffset,
+ bool* aSucceeded) override;
+
+ virtual bool RecvAddToSelection(const uint64_t& aID,
+ const int32_t& aStartOffset,
+ const int32_t& aEndOffset,
+ bool* aSucceeded) override;
+
+ virtual bool RecvRemoveFromSelection(const uint64_t& aID,
+ const int32_t& aSelectionNum,
+ bool* aSucceeded) override;
+
+ virtual bool RecvScrollSubstringTo(const uint64_t& aID,
+ const int32_t& aStartOffset,
+ const int32_t& aEndOffset,
+ const uint32_t& aScrollType) override;
+
+ virtual bool RecvScrollSubstringToPoint(const uint64_t& aID,
+ const int32_t& aStartOffset,
+ const int32_t& aEndOffset,
+ const uint32_t& aCoordinateType,
+ const int32_t& aX,
+ const int32_t& aY) override;
+
+ virtual bool RecvText(const uint64_t& aID,
+ nsString* aText) override;
+
+ virtual bool RecvReplaceText(const uint64_t& aID,
+ const nsString& aText) override;
+
+ virtual bool RecvInsertText(const uint64_t& aID,
+ const nsString& aText,
+ const int32_t& aPosition, bool* aValid) override;
+
+ virtual bool RecvCopyText(const uint64_t& aID,
+ const int32_t& aStartPos,
+ const int32_t& aEndPos, bool* aValid) override;
+
+ virtual bool RecvCutText(const uint64_t& aID,
+ const int32_t& aStartPos,
+ const int32_t& aEndPos, bool* aValid) override;
+
+ virtual bool RecvDeleteText(const uint64_t& aID,
+ const int32_t& aStartPos,
+ const int32_t& aEndPos, bool* aValid) override;
+
+ virtual bool RecvPasteText(const uint64_t& aID,
+ const int32_t& aPosition, bool* aValid) override;
+
+ virtual bool RecvImagePosition(const uint64_t& aID,
+ const uint32_t& aCoordType,
+ nsIntPoint* aRetVal) override;
+
+ virtual bool RecvImageSize(const uint64_t& aID,
+ nsIntSize* aRetVal) override;
+
+ virtual bool RecvStartOffset(const uint64_t& aID,
+ uint32_t* aRetVal,
+ bool* aOk) override;
+ virtual bool RecvEndOffset(const uint64_t& aID,
+ uint32_t* aRetVal,
+ bool* aOk) override;
+ virtual bool RecvIsLinkValid(const uint64_t& aID,
+ bool* aRetVal) override;
+ virtual bool RecvAnchorCount(const uint64_t& aID,
+ uint32_t* aRetVal, bool* aOk) override;
+ virtual bool RecvAnchorURIAt(const uint64_t& aID,
+ const uint32_t& aIndex,
+ nsCString* aURI,
+ bool* aOk) override;
+ virtual bool RecvAnchorAt(const uint64_t& aID,
+ const uint32_t& aIndex,
+ uint64_t* aIDOfAnchor,
+ bool* aOk) override;
+
+ virtual bool RecvLinkCount(const uint64_t& aID,
+ uint32_t* aCount) override;
+
+ virtual bool RecvLinkAt(const uint64_t& aID,
+ const uint32_t& aIndex,
+ uint64_t* aIDOfLink,
+ bool* aOk) override;
+
+ virtual bool RecvLinkIndexOf(const uint64_t& aID,
+ const uint64_t& aLinkID,
+ int32_t* aIndex) override;
+
+ virtual bool RecvLinkIndexAtOffset(const uint64_t& aID,
+ const uint32_t& aOffset,
+ int32_t* aIndex) override;
+
+ virtual bool RecvTableOfACell(const uint64_t& aID,
+ uint64_t* aTableID,
+ bool* aOk) override;
+
+ virtual bool RecvColIdx(const uint64_t& aID, uint32_t* aIndex) override;
+
+ virtual bool RecvRowIdx(const uint64_t& aID, uint32_t* aIndex) override;
+
+ virtual bool RecvColExtent(const uint64_t& aID, uint32_t* aExtent) override;
+
+ virtual bool RecvGetPosition(const uint64_t& aID,
+ uint32_t* aColIdx, uint32_t* aRowIdx) override;
+
+ virtual bool RecvGetColRowExtents(const uint64_t& aID,
+ uint32_t* aColIdx, uint32_t* aRowIdx,
+ uint32_t* aColExtent, uint32_t* aRowExtent) override;
+
+ virtual bool RecvRowExtent(const uint64_t& aID, uint32_t* aExtent) override;
+
+ virtual bool RecvColHeaderCells(const uint64_t& aID,
+ nsTArray<uint64_t>* aCells) override;
+
+ virtual bool RecvRowHeaderCells(const uint64_t& aID,
+ nsTArray<uint64_t>* aCells) override;
+
+ virtual bool RecvIsCellSelected(const uint64_t& aID,
+ bool* aSelected) override;
+
+ virtual bool RecvTableCaption(const uint64_t& aID,
+ uint64_t* aCaptionID,
+ bool* aOk) override;
+ virtual bool RecvTableSummary(const uint64_t& aID,
+ nsString* aSummary) override;
+ virtual bool RecvTableColumnCount(const uint64_t& aID,
+ uint32_t* aColCount) override;
+ virtual bool RecvTableRowCount(const uint64_t& aID,
+ uint32_t* aRowCount) override;
+ virtual bool RecvTableCellAt(const uint64_t& aID,
+ const uint32_t& aRow,
+ const uint32_t& aCol,
+ uint64_t* aCellID,
+ bool* aOk) override;
+ virtual bool RecvTableCellIndexAt(const uint64_t& aID,
+ const uint32_t& aRow,
+ const uint32_t& aCol,
+ int32_t* aIndex) override;
+ virtual bool RecvTableColumnIndexAt(const uint64_t& aID,
+ const uint32_t& aCellIndex,
+ int32_t* aCol) override;
+ virtual bool RecvTableRowIndexAt(const uint64_t& aID,
+ const uint32_t& aCellIndex,
+ int32_t* aRow) override;
+ virtual bool RecvTableRowAndColumnIndicesAt(const uint64_t& aID,
+ const uint32_t& aCellIndex,
+ int32_t* aRow,
+ int32_t* aCol) override;
+ virtual bool RecvTableColumnExtentAt(const uint64_t& aID,
+ const uint32_t& aRow,
+ const uint32_t& aCol,
+ uint32_t* aExtent) override;
+ virtual bool RecvTableRowExtentAt(const uint64_t& aID,
+ const uint32_t& aRow,
+ const uint32_t& aCol,
+ uint32_t* aExtent) override;
+ virtual bool RecvTableColumnDescription(const uint64_t& aID,
+ const uint32_t& aCol,
+ nsString* aDescription) override;
+ virtual bool RecvTableRowDescription(const uint64_t& aID,
+ const uint32_t& aRow,
+ nsString* aDescription) override;
+ virtual bool RecvTableColumnSelected(const uint64_t& aID,
+ const uint32_t& aCol,
+ bool* aSelected) override;
+ virtual bool RecvTableRowSelected(const uint64_t& aID,
+ const uint32_t& aRow,
+ bool* aSelected) override;
+ virtual bool RecvTableCellSelected(const uint64_t& aID,
+ const uint32_t& aRow,
+ const uint32_t& aCol,
+ bool* aSelected) override;
+ virtual bool RecvTableSelectedCellCount(const uint64_t& aID,
+ uint32_t* aSelectedCells) override;
+ virtual bool RecvTableSelectedColumnCount(const uint64_t& aID,
+ uint32_t* aSelectedColumns) override;
+ virtual bool RecvTableSelectedRowCount(const uint64_t& aID,
+ uint32_t* aSelectedRows) override;
+ virtual bool RecvTableSelectedCells(const uint64_t& aID,
+ nsTArray<uint64_t>* aCellIDs) override;
+ virtual bool RecvTableSelectedCellIndices(const uint64_t& aID,
+ nsTArray<uint32_t>* aCellIndices) override;
+ virtual bool RecvTableSelectedColumnIndices(const uint64_t& aID,
+ nsTArray<uint32_t>* aColumnIndices) override;
+ virtual bool RecvTableSelectedRowIndices(const uint64_t& aID,
+ nsTArray<uint32_t>* aRowIndices) override;
+ virtual bool RecvTableSelectColumn(const uint64_t& aID,
+ const uint32_t& aCol) override;
+ virtual bool RecvTableSelectRow(const uint64_t& aID,
+ const uint32_t& aRow) override;
+ virtual bool RecvTableUnselectColumn(const uint64_t& aID,
+ const uint32_t& aCol) override;
+ virtual bool RecvTableUnselectRow(const uint64_t& aID,
+ const uint32_t& aRow) override;
+ virtual bool RecvTableIsProbablyForLayout(const uint64_t& aID,
+ bool* aForLayout) override;
+ virtual bool RecvAtkTableColumnHeader(const uint64_t& aID,
+ const int32_t& aCol,
+ uint64_t* aHeader,
+ bool* aOk) override;
+ virtual bool RecvAtkTableRowHeader(const uint64_t& aID,
+ const int32_t& aRow,
+ uint64_t* aHeader,
+ bool* aOk) override;
+
+ virtual bool RecvSelectedItems(const uint64_t& aID,
+ nsTArray<uint64_t>* aSelectedItemIDs) override;
+
+ virtual bool RecvSelectedItemCount(const uint64_t& aID,
+ uint32_t* aCount) override;
+
+ virtual bool RecvGetSelectedItem(const uint64_t& aID,
+ const uint32_t& aIndex,
+ uint64_t* aSelected,
+ bool* aOk) override;
+
+ virtual bool RecvIsItemSelected(const uint64_t& aID,
+ const uint32_t& aIndex,
+ bool* aSelected) override;
+
+ virtual bool RecvAddItemToSelection(const uint64_t& aID,
+ const uint32_t& aIndex,
+ bool* aSuccess) override;
+
+ virtual bool RecvRemoveItemFromSelection(const uint64_t& aID,
+ const uint32_t& aIndex,
+ bool* aSuccess) override;
+
+ virtual bool RecvSelectAll(const uint64_t& aID,
+ bool* aSuccess) override;
+
+ virtual bool RecvUnselectAll(const uint64_t& aID,
+ bool* aSuccess) override;
+
+ virtual bool RecvTakeSelection(const uint64_t& aID) override;
+ virtual bool RecvSetSelected(const uint64_t& aID,
+ const bool& aSelect) override;
+
+ virtual bool RecvDoAction(const uint64_t& aID,
+ const uint8_t& aIndex,
+ bool* aSuccess) override;
+
+ virtual bool RecvActionCount(const uint64_t& aID,
+ uint8_t* aCount) override;
+
+ virtual bool RecvActionDescriptionAt(const uint64_t& aID,
+ const uint8_t& aIndex,
+ nsString* aDescription) override;
+
+ virtual bool RecvActionNameAt(const uint64_t& aID,
+ const uint8_t& aIndex,
+ nsString* aName) override;
+
+ virtual bool RecvAccessKey(const uint64_t& aID,
+ uint32_t* aKey,
+ uint32_t* aModifierMask) override;
+
+ virtual bool RecvKeyboardShortcut(const uint64_t& aID,
+ uint32_t* aKey,
+ uint32_t* aModifierMask) override;
+
+ virtual bool RecvAtkKeyBinding(const uint64_t& aID,
+ nsString* aResult) override;
+
+ virtual bool RecvCurValue(const uint64_t& aID,
+ double* aValue) override;
+
+ virtual bool RecvSetCurValue(const uint64_t& aID,
+ const double& aValue,
+ bool* aRetVal) override;
+
+ virtual bool RecvMinValue(const uint64_t& aID,
+ double* aValue) override;
+
+ virtual bool RecvMaxValue(const uint64_t& aID,
+ double* aValue) override;
+
+ virtual bool RecvStep(const uint64_t& aID,
+ double* aStep) override;
+
+ virtual bool RecvTakeFocus(const uint64_t& aID) override;
+
+ virtual bool RecvFocusedChild(const uint64_t& aID,
+ uint64_t* aChild,
+ bool* aOk) override;
+
+ virtual bool RecvLanguage(const uint64_t& aID, nsString* aLocale) override;
+ virtual bool RecvDocType(const uint64_t& aID, nsString* aType) override;
+ virtual bool RecvTitle(const uint64_t& aID, nsString* aTitle) override;
+ virtual bool RecvURL(const uint64_t& aID, nsString* aURL) override;
+ virtual bool RecvMimeType(const uint64_t& aID, nsString* aMime) override;
+ virtual bool RecvURLDocTypeMimeType(const uint64_t& aID,
+ nsString* aURL,
+ nsString* aDocType,
+ nsString* aMimeType) override;
+
+ virtual bool RecvAccessibleAtPoint(const uint64_t& aID,
+ const int32_t& aX,
+ const int32_t& aY,
+ const bool& aNeedsScreenCoords,
+ const uint32_t& aWhich,
+ uint64_t* aResult,
+ bool* aOk) override;
+
+ virtual bool RecvExtents(const uint64_t& aID,
+ const bool& aNeedsScreenCoords,
+ int32_t* aX,
+ int32_t* aY,
+ int32_t* aWidth,
+ int32_t* aHeight) override;
+ virtual bool RecvDOMNodeID(const uint64_t& aID, nsString* aDOMNodeID) override;
+private:
+
+ Accessible* IdToAccessible(const uint64_t& aID) const;
+ Accessible* IdToAccessibleLink(const uint64_t& aID) const;
+ Accessible* IdToAccessibleSelect(const uint64_t& aID) const;
+ HyperTextAccessible* IdToHyperTextAccessible(const uint64_t& aID) const;
+ TextLeafAccessible* IdToTextLeafAccessible(const uint64_t& aID) const;
+ ImageAccessible* IdToImageAccessible(const uint64_t& aID) const;
+ TableCellAccessible* IdToTableCellAccessible(const uint64_t& aID) const;
+ TableAccessible* IdToTableAccessible(const uint64_t& aID) const;
+
+ bool PersistentPropertiesToArray(nsIPersistentProperties* aProps,
+ nsTArray<Attribute>* aAttributes);
+};
+
+}
+}
+
+#endif
diff --git a/accessible/ipc/other/PDocAccessible.ipdl b/accessible/ipc/other/PDocAccessible.ipdl
new file mode 100644
index 000000000..1885ed786
--- /dev/null
+++ b/accessible/ipc/other/PDocAccessible.ipdl
@@ -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 protocol PFileDescriptorSet;
+include protocol PBrowser;
+
+include "mozilla/GfxMessageUtils.h";
+
+using nsIntRect from "nsRect.h";
+using mozilla::gfx::IntSize from "mozilla/gfx/Point.h";
+using mozilla::gfx::IntPoint from "mozilla/gfx/Point.h";
+
+namespace mozilla {
+namespace a11y {
+
+struct AccessibleData
+{
+ uint64_t ID;
+ uint32_t Role;
+ uint32_t ChildrenCount;
+ uint32_t Interfaces;
+};
+
+struct ShowEventData
+{
+ uint64_t ID;
+ uint32_t Idx;
+ AccessibleData[] NewTree;
+};
+
+struct Attribute
+{
+ nsCString Name;
+ nsString Value;
+};
+
+struct RelationTargets
+{
+ uint32_t Type;
+ uint64_t[] Targets;
+};
+
+nested(upto inside_sync) sync protocol PDocAccessible
+{
+ manager PBrowser;
+
+parent:
+ async Shutdown();
+
+ /*
+ * Notify the parent process the document in the child process is firing an
+ * event.
+ */
+ async Event(uint64_t aID, uint32_t type);
+ async ShowEvent(ShowEventData data, bool aFromuser);
+ async HideEvent(uint64_t aRootID, bool aFromUser);
+ async StateChangeEvent(uint64_t aID, uint64_t aState, bool aEnabled);
+ async CaretMoveEvent(uint64_t aID, int32_t aOffset);
+ async TextChangeEvent(uint64_t aID, nsString aStr, int32_t aStart, uint32_t aLen,
+ bool aIsInsert, bool aFromUser);
+ async SelectionEvent(uint64_t aID, uint64_t aWidgetID, uint32_t aType);
+ async RoleChangedEvent(uint32_t aRole);
+
+ /*
+ * Tell the parent document to bind the existing document as a new child
+ * document.
+ */
+ async BindChildDoc(PDocAccessible aChildDoc, uint64_t aID);
+
+child:
+ async __delete__();
+
+ // Accessible
+ nested(inside_sync) sync State(uint64_t aID) returns(uint64_t states);
+ nested(inside_sync) sync NativeState(uint64_t aID) returns(uint64_t states);
+ nested(inside_sync) sync Name(uint64_t aID) returns(nsString name);
+ nested(inside_sync) sync Value(uint64_t aID) returns(nsString value);
+ nested(inside_sync) sync Help(uint64_t aID) returns(nsString help);
+ nested(inside_sync) sync Description(uint64_t aID) returns(nsString desc);
+ nested(inside_sync) sync Attributes(uint64_t aID) returns(Attribute[] attributes);
+ nested(inside_sync) sync RelationByType(uint64_t aID, uint32_t aRelationType)
+ returns(uint64_t[] targets);
+ nested(inside_sync) sync Relations(uint64_t aID) returns(RelationTargets[] relations);
+ nested(inside_sync) sync IsSearchbox(uint64_t aID) returns(bool retval);
+ nested(inside_sync) sync LandmarkRole(uint64_t aID) returns(nsString landmark);
+ nested(inside_sync) sync ARIARoleAtom(uint64_t aID) returns(nsString role);
+ nested(inside_sync) sync GetLevelInternal(uint64_t aID) returns(int32_t aLevel);
+ async ScrollTo(uint64_t aID, uint32_t aScrollType);
+ async ScrollToPoint(uint64_t aID, uint32_t aScrollType, int32_t aX,
+ int32_t aY);
+
+ // AccessibleText
+
+ // TextSubstring is getText in IDL.
+ nested(inside_sync) sync CaretLineNumber(uint64_t aID) returns(int32_t aLineNumber);
+ nested(inside_sync) sync CaretOffset(uint64_t aID) returns(int32_t aOffset);
+ async SetCaretOffset(uint64_t aID, int32_t aOffset);
+ nested(inside_sync) sync CharacterCount(uint64_t aID) returns(int32_t aCount);
+ nested(inside_sync) sync SelectionCount(uint64_t aID) returns(int32_t aCount);
+ nested(inside_sync) sync TextSubstring(uint64_t aID, int32_t aStartOffset, int32_t
+ aEndOffset) returns(nsString aText, bool aValid);
+ nested(inside_sync) sync GetTextAfterOffset(uint64_t aID, int32_t aOffset, int32_t aBoundaryType)
+ returns(nsString aText, int32_t aStartOffset, int32_t aEndOffset);
+ nested(inside_sync) sync GetTextAtOffset(uint64_t aID, int32_t aOffset, int32_t aBoundaryType)
+ returns(nsString aText, int32_t aStartOffset, int32_t aEndOffset);
+
+ nested(inside_sync) sync GetTextBeforeOffset(uint64_t aID, int32_t aOffset, int32_t aBoundaryType)
+ returns(nsString aText, int32_t aStartOffset, int32_t aEndOffset);
+ nested(inside_sync) sync CharAt(uint64_t aID, int32_t aOffset) returns(uint16_t aChar);
+
+ nested(inside_sync) sync TextAttributes(uint64_t aID, bool aIncludeDefAttrs, int32_t aOffset)
+ returns(Attribute[] aAttributes, int32_t aStartOffset, int32_t aEndOffset);
+ nested(inside_sync) sync DefaultTextAttributes(uint64_t aID) returns(Attribute[] aAttributes);
+
+ nested(inside_sync) sync TextBounds(uint64_t aID, int32_t aStartOffset, int32_t aEndOffset,
+ uint32_t aCoordType)
+ returns(nsIntRect aRetVal);
+ nested(inside_sync) sync CharBounds(uint64_t aID, int32_t aOffset, uint32_t aCoordType)
+ returns(nsIntRect aRetVal);
+
+ nested(inside_sync) sync OffsetAtPoint(uint64_t aID, int32_t aX, int32_t aY, uint32_t aCoordType)
+ returns(int32_t aRetVal);
+
+ nested(inside_sync) sync SelectionBoundsAt(uint64_t aID, int32_t aSelectionNum)
+ returns(bool aSucceeded, nsString aData, int32_t aStartOffset, int32_t aEndOffset);
+ nested(inside_sync) sync SetSelectionBoundsAt(uint64_t aID, int32_t aSelectionNum,
+ int32_t aStartOffset, int32_t aEndOffset)
+ returns(bool aSucceeded);
+ nested(inside_sync) sync AddToSelection(uint64_t aID, int32_t aStartOffset, int32_t aEndOffset)
+ returns(bool aSucceeded);
+ nested(inside_sync) sync RemoveFromSelection(uint64_t aID, int32_t aSelectionNum)
+ returns(bool aSucceeded);
+
+ async ScrollSubstringTo(uint64_t aID, int32_t aStartOffset, int32_t aEndOffset,
+ uint32_t aScrollType);
+ async ScrollSubstringToPoint(uint64_t aID,
+ int32_t aStartOffset,
+ int32_t aEndOffset,
+ uint32_t aCoordinateType,
+ int32_t aX, int32_t aY);
+
+ nested(inside_sync) sync Text(uint64_t aID) returns(nsString aText);
+ nested(inside_sync) sync ReplaceText(uint64_t aID, nsString aText);
+ nested(inside_sync) sync InsertText(uint64_t aID, nsString aText, int32_t aPosition)
+ returns(bool aValid);
+ nested(inside_sync) sync CopyText(uint64_t aID, int32_t aStartPos, int32_t aEndPos)
+ returns(bool aValid);
+ nested(inside_sync) sync CutText(uint64_t aID, int32_t aStartPos, int32_t aEndPos)
+ returns(bool aValid);
+ nested(inside_sync) sync DeleteText(uint64_t aID, int32_t aStartPos, int32_t aEndPos)
+ returns(bool aValid);
+ nested(inside_sync) sync PasteText(uint64_t aID, int32_t aPosition)
+ returns(bool aValid);
+
+ nested(inside_sync) sync ImagePosition(uint64_t aID, uint32_t aCoordType) returns(IntPoint aRetVal);
+ nested(inside_sync) sync ImageSize(uint64_t aID) returns(IntSize aRetVal);
+
+ nested(inside_sync) sync StartOffset(uint64_t aID) returns(uint32_t aRetVal, bool aOk);
+ nested(inside_sync) sync EndOffset(uint64_t aID) returns(uint32_t aRetVal, bool aOk);
+ nested(inside_sync) sync IsLinkValid(uint64_t aID) returns(bool aRetVal);
+ nested(inside_sync) sync AnchorCount(uint64_t aID) returns(uint32_t aRetVal, bool aOk);
+ nested(inside_sync) sync AnchorURIAt(uint64_t aID, uint32_t aIndex) returns(nsCString aURI, bool aOk);
+ nested(inside_sync) sync AnchorAt(uint64_t aID, uint32_t aIndex) returns(uint64_t aIDOfAnchor, bool aOk);
+
+ nested(inside_sync) sync LinkCount(uint64_t aID) returns(uint32_t aCount);
+ nested(inside_sync) sync LinkAt(uint64_t aID, uint32_t aIndex) returns(uint64_t aIDOfLink, bool aOk);
+ nested(inside_sync) sync LinkIndexOf(uint64_t aID, uint64_t aLinkID) returns(int32_t aIndex);
+ nested(inside_sync) sync LinkIndexAtOffset(uint64_t aID, uint32_t aOffset) returns(int32_t aIndex);
+
+ nested(inside_sync) sync TableOfACell(uint64_t aID) returns(uint64_t aTableID, bool aOk);
+ nested(inside_sync) sync ColIdx(uint64_t aID) returns(uint32_t aIndex);
+ nested(inside_sync) sync RowIdx(uint64_t aID) returns(uint32_t aIndex);
+ nested(inside_sync) sync GetPosition(uint64_t aID) returns(uint32_t aRow, uint32_t aCol);
+ nested(inside_sync) sync ColExtent(uint64_t aID) returns(uint32_t aExtent);
+ nested(inside_sync) sync RowExtent(uint64_t aID) returns(uint32_t aExtent);
+ nested(inside_sync) sync GetColRowExtents(uint64_t aID)
+ returns(uint32_t aCol, uint32_t aRow, uint32_t aColExtent, uint32_t aRowExtent);
+ nested(inside_sync) sync ColHeaderCells(uint64_t aID) returns(uint64_t[] aCells);
+ nested(inside_sync) sync RowHeaderCells(uint64_t aID) returns(uint64_t[] aCells);
+ nested(inside_sync) sync IsCellSelected(uint64_t aID) returns(bool aSelected);
+
+ nested(inside_sync) sync TableCaption(uint64_t aID) returns(uint64_t aCaptionID, bool aOk);
+ nested(inside_sync) sync TableSummary(uint64_t aID) returns(nsString aSummary);
+ nested(inside_sync) sync TableColumnCount(uint64_t aID) returns(uint32_t aColCount);
+ nested(inside_sync) sync TableRowCount(uint64_t aID) returns(uint32_t aRowCount);
+ nested(inside_sync) sync TableCellAt(uint64_t aID, uint32_t aRow, uint32_t aCol) returns(uint64_t aCellID, bool aOk);
+ nested(inside_sync) sync TableCellIndexAt(uint64_t aID, uint32_t aRow, uint32_t aCol) returns(int32_t aIndex);
+ nested(inside_sync) sync TableColumnIndexAt(uint64_t aID, uint32_t aCellIndex) returns(int32_t aCol);
+ nested(inside_sync) sync TableRowIndexAt(uint64_t aID, uint32_t aCellIndex) returns(int32_t aRow);
+ nested(inside_sync) sync TableRowAndColumnIndicesAt(uint64_t aID, uint32_t aCellIndex) returns(int32_t aRow, int32_t aCol);
+ nested(inside_sync) sync TableColumnExtentAt(uint64_t aID, uint32_t aRow, uint32_t aCol) returns(uint32_t aExtent);
+ nested(inside_sync) sync TableRowExtentAt(uint64_t aID, uint32_t aRow, uint32_t aCol) returns(uint32_t aExtent);
+ nested(inside_sync) sync TableColumnDescription(uint64_t aID, uint32_t aCol) returns(nsString aDescription);
+ nested(inside_sync) sync TableRowDescription(uint64_t aID, uint32_t aRow) returns(nsString aDescription);
+ nested(inside_sync) sync TableColumnSelected(uint64_t aID, uint32_t aCol) returns(bool aSelected);
+ nested(inside_sync) sync TableRowSelected(uint64_t aID, uint32_t aRow) returns(bool aSelected);
+ nested(inside_sync) sync TableCellSelected(uint64_t aID, uint32_t aRow, uint32_t aCol) returns(bool aSelected);
+ nested(inside_sync) sync TableSelectedCellCount(uint64_t aID) returns(uint32_t aSelectedCells);
+ nested(inside_sync) sync TableSelectedColumnCount(uint64_t aID) returns(uint32_t aSelectedColumns);
+ nested(inside_sync) sync TableSelectedRowCount(uint64_t aID) returns(uint32_t aSelectedRows);
+ nested(inside_sync) sync TableSelectedCells(uint64_t aID) returns(uint64_t[] aCellIDs);
+ nested(inside_sync) sync TableSelectedCellIndices(uint64_t aID) returns(uint32_t[] aCellIndeces);
+ nested(inside_sync) sync TableSelectedColumnIndices(uint64_t aID) returns(uint32_t[] aColumnIndeces);
+ nested(inside_sync) sync TableSelectedRowIndices(uint64_t aID) returns(uint32_t[] aRowIndeces);
+ nested(inside_sync) sync TableSelectColumn(uint64_t aID, uint32_t aCol);
+ nested(inside_sync) sync TableSelectRow(uint64_t aID, uint32_t aRow);
+ nested(inside_sync) sync TableUnselectColumn(uint64_t aID, uint32_t aCol);
+ nested(inside_sync) sync TableUnselectRow(uint64_t aID, uint32_t aRow);
+ nested(inside_sync) sync TableIsProbablyForLayout(uint64_t aID) returns(bool aForLayout);
+ nested(inside_sync) sync AtkTableColumnHeader(uint64_t aID, int32_t aCol)
+ returns(uint64_t aHeaderID, bool aOk);
+ nested(inside_sync) sync AtkTableRowHeader(uint64_t aID, int32_t aRow)
+ returns(uint64_t aHeaderID, bool aOk);
+
+ nested(inside_sync) sync SelectedItems(uint64_t aID) returns(uint64_t[] aSelectedItemIDs);
+ nested(inside_sync) sync SelectedItemCount(uint64_t aID) returns(uint32_t aCount);
+ nested(inside_sync) sync GetSelectedItem(uint64_t aID, uint32_t aIndex) returns(uint64_t aSelected, bool aOk);
+ nested(inside_sync) sync IsItemSelected(uint64_t aID, uint32_t aIndex) returns(bool aSelected);
+ nested(inside_sync) sync AddItemToSelection(uint64_t aID, uint32_t aIndex) returns(bool aSuccess);
+ nested(inside_sync) sync RemoveItemFromSelection(uint64_t aID, uint32_t aIndex) returns(bool aSuccess);
+ nested(inside_sync) sync SelectAll(uint64_t aID) returns(bool aSuccess);
+ nested(inside_sync) sync UnselectAll(uint64_t aID) returns(bool aSuccess);
+
+ async TakeSelection(uint64_t aID);
+ async SetSelected(uint64_t aID, bool aSelected);
+
+ nested(inside_sync) sync DoAction(uint64_t aID, uint8_t aIndex) returns(bool aSuccess);
+ nested(inside_sync) sync ActionCount(uint64_t aID) returns(uint8_t aCount);
+ nested(inside_sync) sync ActionDescriptionAt(uint64_t aID, uint8_t aIndex) returns(nsString aDescription);
+ nested(inside_sync) sync ActionNameAt(uint64_t aID, uint8_t aIndex) returns(nsString aName);
+ nested(inside_sync) sync AccessKey(uint64_t aID) returns(uint32_t aKey, uint32_t aModifierMask);
+ nested(inside_sync) sync KeyboardShortcut(uint64_t aID) returns(uint32_t aKey, uint32_t aModifierMask);
+ nested(inside_sync) sync AtkKeyBinding(uint64_t aID) returns(nsString aResult);
+
+ nested(inside_sync) sync CurValue(uint64_t aID) returns(double aValue);
+ nested(inside_sync) sync SetCurValue(uint64_t aID, double aValue) returns(bool aRetVal);
+ nested(inside_sync) sync MinValue(uint64_t aID) returns(double aValue);
+ nested(inside_sync) sync MaxValue(uint64_t aID) returns(double aValue);
+ nested(inside_sync) sync Step(uint64_t aID) returns(double aStep);
+
+ async TakeFocus(uint64_t aID);
+ nested(inside_sync) sync FocusedChild(uint64_t aID)
+ returns(uint64_t aChild, bool aOk);
+
+ nested(inside_sync) sync Language(uint64_t aID) returns(nsString aLocale);
+ nested(inside_sync) sync DocType(uint64_t aID) returns(nsString aType);
+ nested(inside_sync) sync Title(uint64_t aID) returns(nsString aTitle);
+ nested(inside_sync) sync URL(uint64_t aID) returns(nsString aURL);
+ nested(inside_sync) sync MimeType(uint64_t aID) returns(nsString aMime);
+ nested(inside_sync) sync URLDocTypeMimeType(uint64_t aID) returns(nsString aURL, nsString aDocType, nsString aMimeType);
+
+ nested(inside_sync) sync AccessibleAtPoint(uint64_t aID, int32_t aX, int32_t aY, bool aNeedsScreenCoords, uint32_t aWhich)
+ returns(uint64_t aResult, bool aOk);
+
+ nested(inside_sync) sync Extents(uint64_t aID, bool aNeedsScreenCoords)
+ returns(int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight);
+ nested(inside_sync) sync DOMNodeID(uint64_t aID) returns(nsString aDOMNodeID);
+};
+
+}
+}
diff --git a/accessible/ipc/other/ProxyAccessible.cpp b/accessible/ipc/other/ProxyAccessible.cpp
new file mode 100644
index 000000000..2eb93dfda
--- /dev/null
+++ b/accessible/ipc/other/ProxyAccessible.cpp
@@ -0,0 +1,1080 @@
+/* -*- 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 "ProxyAccessible.h"
+#include "mozilla/a11y/DocAccessibleParent.h"
+#include "DocAccessible.h"
+#include "mozilla/a11y/DocManager.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/Unused.h"
+#include "mozilla/a11y/Platform.h"
+#include "RelationType.h"
+#include "mozilla/a11y/Role.h"
+#include "xpcAccessibleDocument.h"
+
+namespace mozilla {
+namespace a11y {
+
+uint64_t
+ProxyAccessible::State() const
+{
+ uint64_t state = 0;
+ Unused << mDoc->SendState(mID, &state);
+ return state;
+}
+
+uint64_t
+ProxyAccessible::NativeState() const
+{
+ uint64_t state = 0;
+ Unused << mDoc->SendNativeState(mID, &state);
+ return state;
+}
+
+void
+ProxyAccessible::Name(nsString& aName) const
+{
+ Unused << mDoc->SendName(mID, &aName);
+}
+
+void
+ProxyAccessible::Value(nsString& aValue) const
+{
+ Unused << mDoc->SendValue(mID, &aValue);
+}
+
+void
+ProxyAccessible::Help(nsString& aHelp) const
+{
+ Unused << mDoc->SendHelp(mID, &aHelp);
+}
+
+void
+ProxyAccessible::Description(nsString& aDesc) const
+{
+ Unused << mDoc->SendDescription(mID, &aDesc);
+}
+
+void
+ProxyAccessible::Attributes(nsTArray<Attribute> *aAttrs) const
+{
+ Unused << mDoc->SendAttributes(mID, aAttrs);
+}
+
+nsTArray<ProxyAccessible*>
+ProxyAccessible::RelationByType(RelationType aType) const
+{
+ nsTArray<uint64_t> targetIDs;
+ Unused << mDoc->SendRelationByType(mID, static_cast<uint32_t>(aType),
+ &targetIDs);
+
+ size_t targetCount = targetIDs.Length();
+ nsTArray<ProxyAccessible*> targets(targetCount);
+ for (size_t i = 0; i < targetCount; i++)
+ if (ProxyAccessible* proxy = mDoc->GetAccessible(targetIDs[i]))
+ targets.AppendElement(proxy);
+
+ return Move(targets);
+}
+
+void
+ProxyAccessible::Relations(nsTArray<RelationType>* aTypes,
+ nsTArray<nsTArray<ProxyAccessible*>>* aTargetSets)
+ const
+{
+ nsTArray<RelationTargets> ipcRelations;
+ Unused << mDoc->SendRelations(mID, &ipcRelations);
+
+ size_t relationCount = ipcRelations.Length();
+ aTypes->SetCapacity(relationCount);
+ aTargetSets->SetCapacity(relationCount);
+ for (size_t i = 0; i < relationCount; i++) {
+ uint32_t type = ipcRelations[i].Type();
+ if (type > static_cast<uint32_t>(RelationType::LAST))
+ continue;
+
+ size_t targetCount = ipcRelations[i].Targets().Length();
+ nsTArray<ProxyAccessible*> targets(targetCount);
+ for (size_t j = 0; j < targetCount; j++)
+ if (ProxyAccessible* proxy = mDoc->GetAccessible(ipcRelations[i].Targets()[j]))
+ targets.AppendElement(proxy);
+
+ if (targets.IsEmpty())
+ continue;
+
+ aTargetSets->AppendElement(Move(targets));
+ aTypes->AppendElement(static_cast<RelationType>(type));
+ }
+}
+
+bool
+ProxyAccessible::IsSearchbox() const
+{
+ bool retVal = false;
+ Unused << mDoc->SendIsSearchbox(mID, &retVal);
+ return retVal;
+}
+
+nsIAtom*
+ProxyAccessible::LandmarkRole() const
+{
+ nsString landmark;
+ Unused << mDoc->SendLandmarkRole(mID, &landmark);
+ return NS_GetStaticAtom(landmark);
+}
+
+nsIAtom*
+ProxyAccessible::ARIARoleAtom() const
+{
+ nsString role;
+ Unused << mDoc->SendARIARoleAtom(mID, &role);
+ return NS_GetStaticAtom(role);
+}
+
+int32_t
+ProxyAccessible::GetLevelInternal()
+{
+ int32_t level = 0;
+ Unused << mDoc->SendGetLevelInternal(mID, &level);
+ return level;
+}
+
+void
+ProxyAccessible::ScrollTo(uint32_t aScrollType)
+{
+ Unused << mDoc->SendScrollTo(mID, aScrollType);
+}
+
+void
+ProxyAccessible::ScrollToPoint(uint32_t aScrollType, int32_t aX, int32_t aY)
+{
+ Unused << mDoc->SendScrollToPoint(mID, aScrollType, aX, aY);
+}
+
+int32_t
+ProxyAccessible::CaretLineNumber()
+{
+ int32_t line = -1;
+ Unused << mDoc->SendCaretOffset(mID, &line);
+ return line;
+}
+
+int32_t
+ProxyAccessible::CaretOffset()
+{
+ int32_t offset = 0;
+ Unused << mDoc->SendCaretOffset(mID, &offset);
+ return offset;
+}
+
+void
+ProxyAccessible::SetCaretOffset(int32_t aOffset)
+{
+ Unused << mDoc->SendSetCaretOffset(mID, aOffset);
+}
+
+int32_t
+ProxyAccessible::CharacterCount()
+{
+ int32_t count = 0;
+ Unused << mDoc->SendCharacterCount(mID, &count);
+ return count;
+}
+
+int32_t
+ProxyAccessible::SelectionCount()
+{
+ int32_t count = 0;
+ Unused << mDoc->SendSelectionCount(mID, &count);
+ return count;
+}
+
+bool
+ProxyAccessible::TextSubstring(int32_t aStartOffset, int32_t aEndOfset,
+ nsString& aText) const
+{
+ bool valid;
+ Unused << mDoc->SendTextSubstring(mID, aStartOffset, aEndOfset, &aText, &valid);
+ return valid;
+}
+
+void
+ProxyAccessible::GetTextAfterOffset(int32_t aOffset,
+ AccessibleTextBoundary aBoundaryType,
+ nsString& aText, int32_t* aStartOffset,
+ int32_t* aEndOffset)
+{
+ Unused << mDoc->SendGetTextAfterOffset(mID, aOffset, aBoundaryType,
+ &aText, aStartOffset, aEndOffset);
+}
+
+void
+ProxyAccessible::GetTextAtOffset(int32_t aOffset,
+ AccessibleTextBoundary aBoundaryType,
+ nsString& aText, int32_t* aStartOffset,
+ int32_t* aEndOffset)
+{
+ Unused << mDoc->SendGetTextAtOffset(mID, aOffset, aBoundaryType,
+ &aText, aStartOffset, aEndOffset);
+}
+
+void
+ProxyAccessible::GetTextBeforeOffset(int32_t aOffset,
+ AccessibleTextBoundary aBoundaryType,
+ nsString& aText, int32_t* aStartOffset,
+ int32_t* aEndOffset)
+{
+ Unused << mDoc->SendGetTextBeforeOffset(mID, aOffset, aBoundaryType,
+ &aText, aStartOffset, aEndOffset);
+}
+
+char16_t
+ProxyAccessible::CharAt(int32_t aOffset)
+{
+ uint16_t retval = 0;
+ Unused << mDoc->SendCharAt(mID, aOffset, &retval);
+ return static_cast<char16_t>(retval);
+}
+
+void
+ProxyAccessible::TextAttributes(bool aIncludeDefAttrs,
+ int32_t aOffset,
+ nsTArray<Attribute>* aAttributes,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset)
+{
+ Unused << mDoc->SendTextAttributes(mID, aIncludeDefAttrs, aOffset,
+ aAttributes, aStartOffset, aEndOffset);
+}
+
+void
+ProxyAccessible::DefaultTextAttributes(nsTArray<Attribute>* aAttrs)
+{
+ Unused << mDoc->SendDefaultTextAttributes(mID, aAttrs);
+}
+
+nsIntRect
+ProxyAccessible::TextBounds(int32_t aStartOffset, int32_t aEndOffset,
+ uint32_t aCoordType)
+{
+ nsIntRect rect;
+ Unused <<
+ mDoc->SendTextBounds(mID, aStartOffset, aEndOffset, aCoordType, &rect);
+ return rect;
+}
+
+nsIntRect
+ProxyAccessible::CharBounds(int32_t aOffset, uint32_t aCoordType)
+{
+ nsIntRect rect;
+ Unused <<
+ mDoc->SendCharBounds(mID, aOffset, aCoordType, &rect);
+ return rect;
+}
+
+int32_t
+ProxyAccessible::OffsetAtPoint(int32_t aX, int32_t aY, uint32_t aCoordType)
+{
+ int32_t retVal = -1;
+ Unused << mDoc->SendOffsetAtPoint(mID, aX, aY, aCoordType, &retVal);
+ return retVal;
+}
+
+bool
+ProxyAccessible::SelectionBoundsAt(int32_t aSelectionNum,
+ nsString& aData,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset)
+{
+ bool retVal = false;
+ Unused << mDoc->SendSelectionBoundsAt(mID, aSelectionNum, &retVal, &aData,
+ aStartOffset, aEndOffset);
+ return retVal;
+}
+
+bool
+ProxyAccessible::SetSelectionBoundsAt(int32_t aSelectionNum,
+ int32_t aStartOffset,
+ int32_t aEndOffset)
+{
+ bool retVal = false;
+ Unused << mDoc->SendSetSelectionBoundsAt(mID, aSelectionNum, aStartOffset,
+ aEndOffset, &retVal);
+ return retVal;
+}
+
+bool
+ProxyAccessible::AddToSelection(int32_t aStartOffset,
+ int32_t aEndOffset)
+{
+ bool retVal = false;
+ Unused << mDoc->SendAddToSelection(mID, aStartOffset, aEndOffset, &retVal);
+ return retVal;
+}
+
+bool
+ProxyAccessible::RemoveFromSelection(int32_t aSelectionNum)
+{
+ bool retVal = false;
+ Unused << mDoc->SendRemoveFromSelection(mID, aSelectionNum, &retVal);
+ return retVal;
+}
+
+void
+ProxyAccessible::ScrollSubstringTo(int32_t aStartOffset, int32_t aEndOffset,
+ uint32_t aScrollType)
+{
+ Unused << mDoc->SendScrollSubstringTo(mID, aStartOffset, aEndOffset, aScrollType);
+}
+
+void
+ProxyAccessible::ScrollSubstringToPoint(int32_t aStartOffset,
+ int32_t aEndOffset,
+ uint32_t aCoordinateType,
+ int32_t aX, int32_t aY)
+{
+ Unused << mDoc->SendScrollSubstringToPoint(mID, aStartOffset, aEndOffset,
+ aCoordinateType, aX, aY);
+}
+
+void
+ProxyAccessible::Text(nsString* aText)
+{
+ Unused << mDoc->SendText(mID, aText);
+}
+
+void
+ProxyAccessible::ReplaceText(const nsString& aText)
+{
+ Unused << mDoc->SendReplaceText(mID, aText);
+}
+
+bool
+ProxyAccessible::InsertText(const nsString& aText, int32_t aPosition)
+{
+ bool valid;
+ Unused << mDoc->SendInsertText(mID, aText, aPosition, &valid);
+ return valid;
+}
+
+bool
+ProxyAccessible::CopyText(int32_t aStartPos, int32_t aEndPos)
+{
+ bool valid;
+ Unused << mDoc->SendCopyText(mID, aStartPos, aEndPos, &valid);
+ return valid;
+}
+
+bool
+ProxyAccessible::CutText(int32_t aStartPos, int32_t aEndPos)
+{
+ bool valid;
+ Unused << mDoc->SendCutText(mID, aStartPos, aEndPos, &valid);
+ return valid;
+}
+
+bool
+ProxyAccessible::DeleteText(int32_t aStartPos, int32_t aEndPos)
+{
+ bool valid;
+ Unused << mDoc->SendDeleteText(mID, aStartPos, aEndPos, &valid);
+ return valid;
+}
+
+bool
+ProxyAccessible::PasteText(int32_t aPosition)
+{
+ bool valid;
+ Unused << mDoc->SendPasteText(mID, aPosition, &valid);
+ return valid;
+}
+
+nsIntPoint
+ProxyAccessible::ImagePosition(uint32_t aCoordType)
+{
+ nsIntPoint retVal;
+ Unused << mDoc->SendImagePosition(mID, aCoordType, &retVal);
+ return retVal;
+}
+
+nsIntSize
+ProxyAccessible::ImageSize()
+{
+ nsIntSize retVal;
+ Unused << mDoc->SendImageSize(mID, &retVal);
+ return retVal;
+}
+
+uint32_t
+ProxyAccessible::StartOffset(bool* aOk)
+{
+ uint32_t retVal = 0;
+ Unused << mDoc->SendStartOffset(mID, &retVal, aOk);
+ return retVal;
+}
+
+uint32_t
+ProxyAccessible::EndOffset(bool* aOk)
+{
+ uint32_t retVal = 0;
+ Unused << mDoc->SendEndOffset(mID, &retVal, aOk);
+ return retVal;
+}
+
+bool
+ProxyAccessible::IsLinkValid()
+{
+ bool retVal = false;
+ Unused << mDoc->SendIsLinkValid(mID, &retVal);
+ return retVal;
+}
+
+uint32_t
+ProxyAccessible::AnchorCount(bool* aOk)
+{
+ uint32_t retVal = 0;
+ Unused << mDoc->SendAnchorCount(mID, &retVal, aOk);
+ return retVal;
+}
+
+void
+ProxyAccessible::AnchorURIAt(uint32_t aIndex, nsCString& aURI, bool* aOk)
+{
+ Unused << mDoc->SendAnchorURIAt(mID, aIndex, &aURI, aOk);
+}
+
+ProxyAccessible*
+ProxyAccessible::AnchorAt(uint32_t aIndex)
+{
+ uint64_t id = 0;
+ bool ok = false;
+ Unused << mDoc->SendAnchorAt(mID, aIndex, &id, &ok);
+ return ok ? mDoc->GetAccessible(id) : nullptr;
+}
+
+uint32_t
+ProxyAccessible::LinkCount()
+{
+ uint32_t retVal = 0;
+ Unused << mDoc->SendLinkCount(mID, &retVal);
+ return retVal;
+}
+
+ProxyAccessible*
+ProxyAccessible::LinkAt(const uint32_t& aIndex)
+{
+ uint64_t linkID = 0;
+ bool ok = false;
+ Unused << mDoc->SendLinkAt(mID, aIndex, &linkID, &ok);
+ return ok ? mDoc->GetAccessible(linkID) : nullptr;
+}
+
+int32_t
+ProxyAccessible::LinkIndexOf(ProxyAccessible* aLink)
+{
+ int32_t retVal = -1;
+ if (aLink) {
+ Unused << mDoc->SendLinkIndexOf(mID, aLink->ID(), &retVal);
+ }
+
+ return retVal;
+}
+
+int32_t
+ProxyAccessible::LinkIndexAtOffset(uint32_t aOffset)
+{
+ int32_t retVal = -1;
+ Unused << mDoc->SendLinkIndexAtOffset(mID, aOffset, &retVal);
+ return retVal;
+}
+
+ProxyAccessible*
+ProxyAccessible::TableOfACell()
+{
+ uint64_t tableID = 0;
+ bool ok = false;
+ Unused << mDoc->SendTableOfACell(mID, &tableID, &ok);
+ return ok ? mDoc->GetAccessible(tableID) : nullptr;
+}
+
+uint32_t
+ProxyAccessible::ColIdx()
+{
+ uint32_t index = 0;
+ Unused << mDoc->SendColIdx(mID, &index);
+ return index;
+}
+
+uint32_t
+ProxyAccessible::RowIdx()
+{
+ uint32_t index = 0;
+ Unused << mDoc->SendRowIdx(mID, &index);
+ return index;
+}
+
+void
+ProxyAccessible::GetColRowExtents(uint32_t* aColIdx, uint32_t* aRowIdx,
+ uint32_t* aColExtent, uint32_t* aRowExtent)
+{
+ Unused << mDoc->SendGetColRowExtents(mID, aColIdx, aRowIdx, aColExtent, aRowExtent);
+}
+
+void
+ProxyAccessible::GetPosition(uint32_t* aColIdx, uint32_t* aRowIdx)
+{
+ Unused << mDoc->SendGetPosition(mID, aColIdx, aRowIdx);
+}
+
+uint32_t
+ProxyAccessible::ColExtent()
+{
+ uint32_t extent = 0;
+ Unused << mDoc->SendColExtent(mID, &extent);
+ return extent;
+}
+
+uint32_t
+ProxyAccessible::RowExtent()
+{
+ uint32_t extent = 0;
+ Unused << mDoc->SendRowExtent(mID, &extent);
+ return extent;
+}
+
+void
+ProxyAccessible::ColHeaderCells(nsTArray<ProxyAccessible*>* aCells)
+{
+ nsTArray<uint64_t> targetIDs;
+ Unused << mDoc->SendColHeaderCells(mID, &targetIDs);
+
+ size_t targetCount = targetIDs.Length();
+ for (size_t i = 0; i < targetCount; i++) {
+ aCells->AppendElement(mDoc->GetAccessible(targetIDs[i]));
+ }
+}
+
+void
+ProxyAccessible::RowHeaderCells(nsTArray<ProxyAccessible*>* aCells)
+{
+ nsTArray<uint64_t> targetIDs;
+ Unused << mDoc->SendRowHeaderCells(mID, &targetIDs);
+
+ size_t targetCount = targetIDs.Length();
+ for (size_t i = 0; i < targetCount; i++) {
+ aCells->AppendElement(mDoc->GetAccessible(targetIDs[i]));
+ }
+}
+
+bool
+ProxyAccessible::IsCellSelected()
+{
+ bool selected = false;
+ Unused << mDoc->SendIsCellSelected(mID, &selected);
+ return selected;
+}
+
+ProxyAccessible*
+ProxyAccessible::TableCaption()
+{
+ uint64_t captionID = 0;
+ bool ok = false;
+ Unused << mDoc->SendTableCaption(mID, &captionID, &ok);
+ return ok ? mDoc->GetAccessible(captionID) : nullptr;
+}
+
+void
+ProxyAccessible::TableSummary(nsString& aSummary)
+{
+ Unused << mDoc->SendTableSummary(mID, &aSummary);
+}
+
+uint32_t
+ProxyAccessible::TableColumnCount()
+{
+ uint32_t count = 0;
+ Unused << mDoc->SendTableColumnCount(mID, &count);
+ return count;
+}
+
+uint32_t
+ProxyAccessible::TableRowCount()
+{
+ uint32_t count = 0;
+ Unused << mDoc->SendTableRowCount(mID, &count);
+ return count;
+}
+
+ProxyAccessible*
+ProxyAccessible::TableCellAt(uint32_t aRow, uint32_t aCol)
+{
+ uint64_t cellID = 0;
+ bool ok = false;
+ Unused << mDoc->SendTableCellAt(mID, aRow, aCol, &cellID, &ok);
+ return ok ? mDoc->GetAccessible(cellID) : nullptr;
+}
+
+int32_t
+ProxyAccessible::TableCellIndexAt(uint32_t aRow, uint32_t aCol)
+{
+ int32_t index = 0;
+ Unused << mDoc->SendTableCellIndexAt(mID, aRow, aCol, &index);
+ return index;
+}
+
+int32_t
+ProxyAccessible::TableColumnIndexAt(uint32_t aCellIndex)
+{
+ int32_t index = 0;
+ Unused << mDoc->SendTableColumnIndexAt(mID, aCellIndex, &index);
+ return index;
+}
+
+int32_t
+ProxyAccessible::TableRowIndexAt(uint32_t aCellIndex)
+{
+ int32_t index = 0;
+ Unused << mDoc->SendTableRowIndexAt(mID, aCellIndex, &index);
+ return index;
+}
+
+void
+ProxyAccessible::TableRowAndColumnIndicesAt(uint32_t aCellIndex,
+ int32_t* aRow, int32_t* aCol)
+{
+ Unused << mDoc->SendTableRowAndColumnIndicesAt(mID, aCellIndex, aRow, aCol);
+}
+
+uint32_t
+ProxyAccessible::TableColumnExtentAt(uint32_t aRow, uint32_t aCol)
+{
+ uint32_t extent = 0;
+ Unused << mDoc->SendTableColumnExtentAt(mID, aRow, aCol, &extent);
+ return extent;
+}
+
+uint32_t
+ProxyAccessible::TableRowExtentAt(uint32_t aRow, uint32_t aCol)
+{
+ uint32_t extent = 0;
+ Unused << mDoc->SendTableRowExtentAt(mID, aRow, aCol, &extent);
+ return extent;
+}
+
+void
+ProxyAccessible::TableColumnDescription(uint32_t aCol, nsString& aDescription)
+{
+ Unused << mDoc->SendTableColumnDescription(mID, aCol, &aDescription);
+}
+
+void
+ProxyAccessible::TableRowDescription(uint32_t aRow, nsString& aDescription)
+{
+ Unused << mDoc->SendTableRowDescription(mID, aRow, &aDescription);
+}
+
+bool
+ProxyAccessible::TableColumnSelected(uint32_t aCol)
+{
+ bool selected = false;
+ Unused << mDoc->SendTableColumnSelected(mID, aCol, &selected);
+ return selected;
+}
+
+bool
+ProxyAccessible::TableRowSelected(uint32_t aRow)
+{
+ bool selected = false;
+ Unused << mDoc->SendTableRowSelected(mID, aRow, &selected);
+ return selected;
+}
+
+bool
+ProxyAccessible::TableCellSelected(uint32_t aRow, uint32_t aCol)
+{
+ bool selected = false;
+ Unused << mDoc->SendTableCellSelected(mID, aRow, aCol, &selected);
+ return selected;
+}
+
+uint32_t
+ProxyAccessible::TableSelectedCellCount()
+{
+ uint32_t count = 0;
+ Unused << mDoc->SendTableSelectedCellCount(mID, &count);
+ return count;
+}
+
+uint32_t
+ProxyAccessible::TableSelectedColumnCount()
+{
+ uint32_t count = 0;
+ Unused << mDoc->SendTableSelectedColumnCount(mID, &count);
+ return count;
+}
+
+uint32_t
+ProxyAccessible::TableSelectedRowCount()
+{
+ uint32_t count = 0;
+ Unused << mDoc->SendTableSelectedRowCount(mID, &count);
+ return count;
+}
+
+void
+ProxyAccessible::TableSelectedCells(nsTArray<ProxyAccessible*>* aCellIDs)
+{
+ AutoTArray<uint64_t, 30> cellIDs;
+ Unused << mDoc->SendTableSelectedCells(mID, &cellIDs);
+ aCellIDs->SetCapacity(cellIDs.Length());
+ for (uint32_t i = 0; i < cellIDs.Length(); ++i) {
+ aCellIDs->AppendElement(mDoc->GetAccessible(cellIDs[i]));
+ }
+}
+
+void
+ProxyAccessible::TableSelectedCellIndices(nsTArray<uint32_t>* aCellIndices)
+{
+ Unused << mDoc->SendTableSelectedCellIndices(mID, aCellIndices);
+}
+
+void
+ProxyAccessible::TableSelectedColumnIndices(nsTArray<uint32_t>* aColumnIndices)
+{
+ Unused << mDoc->SendTableSelectedColumnIndices(mID, aColumnIndices);
+}
+
+void
+ProxyAccessible::TableSelectedRowIndices(nsTArray<uint32_t>* aRowIndices)
+{
+ Unused << mDoc->SendTableSelectedRowIndices(mID, aRowIndices);
+}
+
+void
+ProxyAccessible::TableSelectColumn(uint32_t aCol)
+{
+ Unused << mDoc->SendTableSelectColumn(mID, aCol);
+}
+
+void
+ProxyAccessible::TableSelectRow(uint32_t aRow)
+{
+ Unused << mDoc->SendTableSelectRow(mID, aRow);
+}
+
+void
+ProxyAccessible::TableUnselectColumn(uint32_t aCol)
+{
+ Unused << mDoc->SendTableUnselectColumn(mID, aCol);
+}
+
+void
+ProxyAccessible::TableUnselectRow(uint32_t aRow)
+{
+ Unused << mDoc->SendTableUnselectRow(mID, aRow);
+}
+
+bool
+ProxyAccessible::TableIsProbablyForLayout()
+{
+ bool forLayout = false;
+ Unused << mDoc->SendTableIsProbablyForLayout(mID, &forLayout);
+ return forLayout;
+}
+
+ProxyAccessible*
+ProxyAccessible::AtkTableColumnHeader(int32_t aCol)
+{
+ uint64_t headerID = 0;
+ bool ok = false;
+ Unused << mDoc->SendAtkTableColumnHeader(mID, aCol, &headerID, &ok);
+ return ok ? mDoc->GetAccessible(headerID) : nullptr;
+}
+
+ProxyAccessible*
+ProxyAccessible::AtkTableRowHeader(int32_t aRow)
+{
+ uint64_t headerID = 0;
+ bool ok = false;
+ Unused << mDoc->SendAtkTableRowHeader(mID, aRow, &headerID, &ok);
+ return ok ? mDoc->GetAccessible(headerID) : nullptr;
+}
+
+void
+ProxyAccessible::SelectedItems(nsTArray<ProxyAccessible*>* aSelectedItems)
+{
+ AutoTArray<uint64_t, 10> itemIDs;
+ Unused << mDoc->SendSelectedItems(mID, &itemIDs);
+ aSelectedItems->SetCapacity(itemIDs.Length());
+ for (size_t i = 0; i < itemIDs.Length(); ++i) {
+ aSelectedItems->AppendElement(mDoc->GetAccessible(itemIDs[i]));
+ }
+}
+
+uint32_t
+ProxyAccessible::SelectedItemCount()
+{
+ uint32_t count = 0;
+ Unused << mDoc->SendSelectedItemCount(mID, &count);
+ return count;
+}
+
+ProxyAccessible*
+ProxyAccessible::GetSelectedItem(uint32_t aIndex)
+{
+ uint64_t selectedItemID = 0;
+ bool ok = false;
+ Unused << mDoc->SendGetSelectedItem(mID, aIndex, &selectedItemID, &ok);
+ return ok ? mDoc->GetAccessible(selectedItemID) : nullptr;
+}
+
+bool
+ProxyAccessible::IsItemSelected(uint32_t aIndex)
+{
+ bool selected = false;
+ Unused << mDoc->SendIsItemSelected(mID, aIndex, &selected);
+ return selected;
+}
+
+bool
+ProxyAccessible::AddItemToSelection(uint32_t aIndex)
+{
+ bool success = false;
+ Unused << mDoc->SendAddItemToSelection(mID, aIndex, &success);
+ return success;
+}
+
+bool
+ProxyAccessible::RemoveItemFromSelection(uint32_t aIndex)
+{
+ bool success = false;
+ Unused << mDoc->SendRemoveItemFromSelection(mID, aIndex, &success);
+ return success;
+}
+
+bool
+ProxyAccessible::SelectAll()
+{
+ bool success = false;
+ Unused << mDoc->SendSelectAll(mID, &success);
+ return success;
+}
+
+bool
+ProxyAccessible::UnselectAll()
+{
+ bool success = false;
+ Unused << mDoc->SendUnselectAll(mID, &success);
+ return success;
+}
+
+void
+ProxyAccessible::TakeSelection()
+{
+ Unused << mDoc->SendTakeSelection(mID);
+}
+
+void
+ProxyAccessible::SetSelected(bool aSelect)
+{
+ Unused << mDoc->SendSetSelected(mID, aSelect);
+}
+
+bool
+ProxyAccessible::DoAction(uint8_t aIndex)
+{
+ bool success = false;
+ Unused << mDoc->SendDoAction(mID, aIndex, &success);
+ return success;
+}
+
+uint8_t
+ProxyAccessible::ActionCount()
+{
+ uint8_t count = 0;
+ Unused << mDoc->SendActionCount(mID, &count);
+ return count;
+}
+
+void
+ProxyAccessible::ActionDescriptionAt(uint8_t aIndex, nsString& aDescription)
+{
+ Unused << mDoc->SendActionDescriptionAt(mID, aIndex, &aDescription);
+}
+
+void
+ProxyAccessible::ActionNameAt(uint8_t aIndex, nsString& aName)
+{
+ Unused << mDoc->SendActionNameAt(mID, aIndex, &aName);
+}
+
+KeyBinding
+ProxyAccessible::AccessKey()
+{
+ uint32_t key = 0;
+ uint32_t modifierMask = 0;
+ Unused << mDoc->SendAccessKey(mID, &key, &modifierMask);
+ return KeyBinding(key, modifierMask);
+}
+
+KeyBinding
+ProxyAccessible::KeyboardShortcut()
+{
+ uint32_t key = 0;
+ uint32_t modifierMask = 0;
+ Unused << mDoc->SendKeyboardShortcut(mID, &key, &modifierMask);
+ return KeyBinding(key, modifierMask);
+}
+
+void
+ProxyAccessible::AtkKeyBinding(nsString& aBinding)
+{
+ Unused << mDoc->SendAtkKeyBinding(mID, &aBinding);
+}
+
+double
+ProxyAccessible::CurValue()
+{
+ double val = UnspecifiedNaN<double>();
+ Unused << mDoc->SendCurValue(mID, &val);
+ return val;
+}
+
+bool
+ProxyAccessible::SetCurValue(double aValue)
+{
+ bool success = false;
+ Unused << mDoc->SendSetCurValue(mID, aValue, &success);
+ return success;
+}
+
+double
+ProxyAccessible::MinValue()
+{
+ double val = UnspecifiedNaN<double>();
+ Unused << mDoc->SendMinValue(mID, &val);
+ return val;
+}
+
+double
+ProxyAccessible::MaxValue()
+{
+ double val = UnspecifiedNaN<double>();
+ Unused << mDoc->SendMaxValue(mID, &val);
+ return val;
+}
+
+double
+ProxyAccessible::Step()
+{
+ double step = UnspecifiedNaN<double>();
+ Unused << mDoc->SendStep(mID, &step);
+ return step;
+}
+
+void
+ProxyAccessible::TakeFocus()
+{
+ Unused << mDoc->SendTakeFocus(mID);
+}
+
+ProxyAccessible*
+ProxyAccessible::FocusedChild()
+{
+ uint64_t childID = 0;
+ bool ok = false;
+ Unused << mDoc->SendFocusedChild(mID, &childID, &ok);
+ return ok ? mDoc->GetAccessible(childID) : nullptr;
+}
+
+ProxyAccessible*
+ProxyAccessible::ChildAtPoint(int32_t aX, int32_t aY,
+ Accessible::EWhichChildAtPoint aWhichChild)
+{
+ uint64_t childID = 0;
+ bool ok = false;
+ Unused << mDoc->SendAccessibleAtPoint(mID, aX, aY, false,
+ static_cast<uint32_t>(aWhichChild),
+ &childID, &ok);
+ return ok ? mDoc->GetAccessible(childID) : nullptr;
+}
+
+nsIntRect
+ProxyAccessible::Bounds()
+{
+ nsIntRect rect;
+ Unused << mDoc->SendExtents(mID, false,
+ &(rect.x), &(rect.y),
+ &(rect.width), &(rect.height));
+ return rect;
+}
+
+void
+ProxyAccessible::Language(nsString& aLocale)
+{
+ Unused << mDoc->SendLanguage(mID, &aLocale);
+}
+
+void
+ProxyAccessible::DocType(nsString& aType)
+{
+ Unused << mDoc->SendDocType(mID, &aType);
+}
+
+void
+ProxyAccessible::Title(nsString& aTitle)
+{
+ Unused << mDoc->SendTitle(mID, &aTitle);
+}
+
+void
+ProxyAccessible::URL(nsString& aURL)
+{
+ Unused << mDoc->SendURL(mID, &aURL);
+}
+
+void
+ProxyAccessible::MimeType(nsString aMime)
+{
+ Unused << mDoc->SendMimeType(mID, &aMime);
+}
+
+void
+ProxyAccessible::URLDocTypeMimeType(nsString& aURL, nsString& aDocType,
+ nsString& aMimeType)
+{
+ Unused << mDoc->SendURLDocTypeMimeType(mID, &aURL, &aDocType, &aMimeType);
+}
+
+ProxyAccessible*
+ProxyAccessible::AccessibleAtPoint(int32_t aX, int32_t aY,
+ bool aNeedsScreenCoords)
+{
+ uint64_t childID = 0;
+ bool ok = false;
+ Unused <<
+ mDoc->SendAccessibleAtPoint(mID, aX, aY, aNeedsScreenCoords,
+ static_cast<uint32_t>(Accessible::eDirectChild),
+ &childID, &ok);
+ return ok ? mDoc->GetAccessible(childID) : nullptr;
+}
+
+void
+ProxyAccessible::Extents(bool aNeedsScreenCoords, int32_t* aX, int32_t* aY,
+ int32_t* aWidth, int32_t* aHeight)
+{
+ Unused << mDoc->SendExtents(mID, aNeedsScreenCoords, aX, aY, aWidth, aHeight);
+}
+
+void
+ProxyAccessible::DOMNodeID(nsString& aID)
+{
+ Unused << mDoc->SendDOMNodeID(mID, &aID);
+}
+
+}
+}
diff --git a/accessible/ipc/other/ProxyAccessible.h b/accessible/ipc/other/ProxyAccessible.h
new file mode 100644
index 000000000..25fd71fbf
--- /dev/null
+++ b/accessible/ipc/other/ProxyAccessible.h
@@ -0,0 +1,50 @@
+/* -*- 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_ProxyAccessible_h
+#define mozilla_a11y_ProxyAccessible_h
+
+#include "Accessible.h"
+#include "mozilla/a11y/ProxyAccessibleBase.h"
+#include "mozilla/a11y/Role.h"
+#include "nsIAccessibleText.h"
+#include "nsIAccessibleTypes.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsRect.h"
+
+namespace mozilla {
+namespace a11y {
+
+class ProxyAccessible : public ProxyAccessibleBase<ProxyAccessible>
+{
+public:
+
+ ProxyAccessible(uint64_t aID, ProxyAccessible* aParent,
+ DocAccessibleParent* aDoc, role aRole, uint32_t aInterfaces)
+ : ProxyAccessibleBase(aID, aParent, aDoc, aRole, aInterfaces)
+
+ {
+ MOZ_COUNT_CTOR(ProxyAccessible);
+ }
+
+ ~ProxyAccessible()
+ {
+ MOZ_COUNT_DTOR(ProxyAccessible);
+ }
+
+#include "mozilla/a11y/ProxyAccessibleShared.h"
+
+protected:
+ explicit ProxyAccessible(DocAccessibleParent* aThisAsDoc)
+ : ProxyAccessibleBase(aThisAsDoc)
+ { MOZ_COUNT_CTOR(ProxyAccessible); }
+};
+
+}
+}
+
+#endif
diff --git a/accessible/ipc/other/moz.build b/accessible/ipc/other/moz.build
new file mode 100644
index 000000000..65021145c
--- /dev/null
+++ b/accessible/ipc/other/moz.build
@@ -0,0 +1,47 @@
+# -*- 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/.
+
+# With --disable-accessibility, we need to compile PDocAccessible.ipdl, but
+# not the C++.
+IPDL_SOURCES += ['PDocAccessible.ipdl']
+
+if CONFIG['ACCESSIBILITY']:
+ EXPORTS.mozilla.a11y += [
+ 'DocAccessibleChild.h',
+ 'ProxyAccessible.h',
+ ]
+
+ SOURCES += [
+ 'DocAccessibleChild.cpp',
+ 'ProxyAccessible.cpp',
+ ]
+
+ LOCAL_INCLUDES += [
+ '../../base',
+ '../../generic',
+ '../../xpcom',
+ ]
+
+ if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ LOCAL_INCLUDES += [
+ '/accessible/atk',
+ ]
+ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ LOCAL_INCLUDES += [
+ '/accessible/mac',
+ ]
+ else:
+ LOCAL_INCLUDES += [
+ '/accessible/other',
+ ]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
+
+FINAL_LIBRARY = 'xul'
+
diff --git a/accessible/ipc/win/COMPtrTypes.cpp b/accessible/ipc/win/COMPtrTypes.cpp
new file mode 100644
index 000000000..857f4235e
--- /dev/null
+++ b/accessible/ipc/win/COMPtrTypes.cpp
@@ -0,0 +1,50 @@
+/* -*- 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 "mozilla/a11y/COMPtrTypes.h"
+
+#include "MainThreadUtils.h"
+#include "mozilla/a11y/Accessible.h"
+#include "mozilla/Move.h"
+#include "mozilla/mscom/MainThreadHandoff.h"
+#include "mozilla/RefPtr.h"
+
+using mozilla::mscom::MainThreadHandoff;
+using mozilla::mscom::STAUniquePtr;
+
+namespace mozilla {
+namespace a11y {
+
+IAccessibleHolder
+CreateHolderFromAccessible(Accessible* aAccToWrap)
+{
+ MOZ_ASSERT(aAccToWrap && NS_IsMainThread());
+ if (!aAccToWrap) {
+ return nullptr;
+ }
+
+ IAccessible* rawNative = nullptr;
+ aAccToWrap->GetNativeInterface((void**)&rawNative);
+ MOZ_ASSERT(rawNative);
+ if (!rawNative) {
+ return nullptr;
+ }
+
+ STAUniquePtr<IAccessible> iaToProxy(rawNative);
+
+ IAccessible* rawIntercepted = nullptr;
+ HRESULT hr = MainThreadHandoff::WrapInterface(Move(iaToProxy), &rawIntercepted);
+ MOZ_ASSERT(SUCCEEDED(hr));
+ if (FAILED(hr)) {
+ return nullptr;
+ }
+
+ IAccessibleHolder::COMPtrType iaIntercepted(rawIntercepted);
+ return IAccessibleHolder(Move(iaIntercepted));
+}
+
+} // namespace a11y
+} // namespace mozilla
diff --git a/accessible/ipc/win/COMPtrTypes.h b/accessible/ipc/win/COMPtrTypes.h
new file mode 100644
index 000000000..122e1ea5e
--- /dev/null
+++ b/accessible/ipc/win/COMPtrTypes.h
@@ -0,0 +1,27 @@
+/* -*- 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_COMPtrTypes_h
+#define mozilla_a11y_COMPtrTypes_h
+
+#include "mozilla/mscom/COMPtrHolder.h"
+
+#include <oleacc.h>
+
+namespace mozilla {
+namespace a11y {
+
+typedef mozilla::mscom::COMPtrHolder<IAccessible, IID_IAccessible> IAccessibleHolder;
+
+class Accessible;
+
+IAccessibleHolder
+CreateHolderFromAccessible(Accessible* aAccToWrap);
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_COMPtrTypes_h
diff --git a/accessible/ipc/win/DocAccessibleChild.cpp b/accessible/ipc/win/DocAccessibleChild.cpp
new file mode 100644
index 000000000..e8b8bebe5
--- /dev/null
+++ b/accessible/ipc/win/DocAccessibleChild.cpp
@@ -0,0 +1,237 @@
+/* -*- 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 "DocAccessibleChild.h"
+
+#include "Accessible-inl.h"
+#include "mozilla/a11y/PlatformChild.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "RootAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+static StaticAutoPtr<PlatformChild> sPlatformChild;
+
+DocAccessibleChild::DocAccessibleChild(DocAccessible* aDoc)
+ : DocAccessibleChildBase(aDoc)
+ , mIsRemoteConstructed(false)
+{
+ MOZ_COUNT_CTOR_INHERITED(DocAccessibleChild, DocAccessibleChildBase);
+ if (!sPlatformChild) {
+ sPlatformChild = new PlatformChild();
+ ClearOnShutdown(&sPlatformChild, ShutdownPhase::Shutdown);
+ }
+}
+
+DocAccessibleChild::~DocAccessibleChild()
+{
+ MOZ_COUNT_DTOR_INHERITED(DocAccessibleChild, DocAccessibleChildBase);
+}
+
+void
+DocAccessibleChild::Shutdown()
+{
+ if (IsConstructedInParentProcess()) {
+ DocAccessibleChildBase::Shutdown();
+ return;
+ }
+
+ PushDeferredEvent(MakeUnique<SerializedShutdown>(this));
+ DetachDocument();
+}
+
+bool
+DocAccessibleChild::RecvParentCOMProxy(const IAccessibleHolder& aParentCOMProxy)
+{
+ MOZ_ASSERT(!mParentProxy && !aParentCOMProxy.IsNull());
+ mParentProxy.reset(const_cast<IAccessibleHolder&>(aParentCOMProxy).Release());
+ SetConstructedInParentProcess();
+
+ for (uint32_t i = 0, l = mDeferredEvents.Length(); i < l; ++i) {
+ mDeferredEvents[i]->Dispatch();
+ }
+
+ mDeferredEvents.Clear();
+
+ return true;
+}
+
+void
+DocAccessibleChild::PushDeferredEvent(UniquePtr<DeferredEvent> aEvent)
+{
+ DocAccessibleChild* topLevelIPCDoc = nullptr;
+
+ if (mDoc && mDoc->IsRoot()) {
+ topLevelIPCDoc = this;
+ } else {
+ auto tabChild = static_cast<dom::TabChild*>(Manager());
+ if (!tabChild) {
+ return;
+ }
+
+ nsTArray<PDocAccessibleChild*> ipcDocAccs;
+ tabChild->ManagedPDocAccessibleChild(ipcDocAccs);
+
+ // Look for the top-level DocAccessibleChild - there will only be one
+ // per TabChild.
+ for (uint32_t i = 0, l = ipcDocAccs.Length(); i < l; ++i) {
+ auto ipcDocAcc = static_cast<DocAccessibleChild*>(ipcDocAccs[i]);
+ if (ipcDocAcc->mDoc && ipcDocAcc->mDoc->IsRoot()) {
+ topLevelIPCDoc = ipcDocAcc;
+ break;
+ }
+ }
+ }
+
+ if (topLevelIPCDoc) {
+ topLevelIPCDoc->mDeferredEvents.AppendElement(Move(aEvent));
+ }
+}
+
+bool
+DocAccessibleChild::SendEvent(const uint64_t& aID, const uint32_t& aType)
+{
+ if (IsConstructedInParentProcess()) {
+ return PDocAccessibleChild::SendEvent(aID, aType);
+ }
+
+ PushDeferredEvent(MakeUnique<SerializedEvent>(this, aID, aType));
+ return false;
+}
+
+void
+DocAccessibleChild::MaybeSendShowEvent(ShowEventData& aData, bool aFromUser)
+{
+ if (IsConstructedInParentProcess()) {
+ Unused << SendShowEvent(aData, aFromUser);
+ return;
+ }
+
+ PushDeferredEvent(MakeUnique<SerializedShow>(this, aData, aFromUser));
+}
+
+bool
+DocAccessibleChild::SendHideEvent(const uint64_t& aRootID,
+ const bool& aFromUser)
+{
+ if (IsConstructedInParentProcess()) {
+ return PDocAccessibleChild::SendHideEvent(aRootID, aFromUser);
+ }
+
+ PushDeferredEvent(MakeUnique<SerializedHide>(this, aRootID, aFromUser));
+ return true;
+}
+
+bool
+DocAccessibleChild::SendStateChangeEvent(const uint64_t& aID,
+ const uint64_t& aState,
+ const bool& aEnabled)
+{
+ if (IsConstructedInParentProcess()) {
+ return PDocAccessibleChild::SendStateChangeEvent(aID, aState, aEnabled);
+ }
+
+ PushDeferredEvent(MakeUnique<SerializedStateChange>(this, aID, aState,
+ aEnabled));
+ return true;
+}
+
+bool
+DocAccessibleChild::SendCaretMoveEvent(const uint64_t& aID,
+ const int32_t& aOffset)
+{
+ if (IsConstructedInParentProcess()) {
+ return PDocAccessibleChild::SendCaretMoveEvent(aID, aOffset);
+ }
+
+ PushDeferredEvent(MakeUnique<SerializedCaretMove>(this, aID, aOffset));
+ return true;
+}
+
+bool
+DocAccessibleChild::SendTextChangeEvent(const uint64_t& aID,
+ const nsString& aStr,
+ const int32_t& aStart,
+ const uint32_t& aLen,
+ const bool& aIsInsert,
+ const bool& aFromUser)
+{
+ if (IsConstructedInParentProcess()) {
+ return PDocAccessibleChild::SendTextChangeEvent(aID, aStr, aStart,
+ aLen, aIsInsert, aFromUser);
+ }
+
+ PushDeferredEvent(MakeUnique<SerializedTextChange>(this, aID, aStr, aStart,
+ aLen, aIsInsert, aFromUser));
+ return true;
+}
+
+bool
+DocAccessibleChild::SendSelectionEvent(const uint64_t& aID,
+ const uint64_t& aWidgetID,
+ const uint32_t& aType)
+{
+ if (IsConstructedInParentProcess()) {
+ return PDocAccessibleChild::SendSelectionEvent(aID, aWidgetID, aType);
+ }
+
+ PushDeferredEvent(MakeUnique<SerializedSelection>(this, aID,
+ aWidgetID,
+ aType));
+ return true;
+}
+
+bool
+DocAccessibleChild::SendRoleChangedEvent(const uint32_t& aRole)
+{
+ if (IsConstructedInParentProcess()) {
+ return PDocAccessibleChild::SendRoleChangedEvent(aRole);
+ }
+
+ PushDeferredEvent(MakeUnique<SerializedRoleChanged>(this, aRole));
+ return true;
+}
+
+bool
+DocAccessibleChild::ConstructChildDocInParentProcess(
+ DocAccessibleChild* aNewChildDoc,
+ uint64_t aUniqueID, uint32_t aMsaaID)
+{
+ if (IsConstructedInParentProcess()) {
+ // We may send the constructor immediately
+ auto tabChild = static_cast<dom::TabChild*>(Manager());
+ MOZ_ASSERT(tabChild);
+ bool result = tabChild->SendPDocAccessibleConstructor(aNewChildDoc, this,
+ aUniqueID, aMsaaID,
+ IAccessibleHolder());
+ if (result) {
+ aNewChildDoc->SetConstructedInParentProcess();
+ }
+ return result;
+ }
+
+ PushDeferredEvent(MakeUnique<SerializedChildDocConstructor>(aNewChildDoc, this,
+ aUniqueID, aMsaaID));
+ return true;
+}
+
+bool
+DocAccessibleChild::SendBindChildDoc(DocAccessibleChild* aChildDoc,
+ const uint64_t& aNewParentID)
+{
+ if (IsConstructedInParentProcess()) {
+ return DocAccessibleChildBase::SendBindChildDoc(aChildDoc, aNewParentID);
+ }
+
+ PushDeferredEvent(MakeUnique<SerializedBindChildDoc>(this, aChildDoc,
+ aNewParentID));
+ return true;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
diff --git a/accessible/ipc/win/DocAccessibleChild.h b/accessible/ipc/win/DocAccessibleChild.h
new file mode 100644
index 000000000..7a3da0172
--- /dev/null
+++ b/accessible/ipc/win/DocAccessibleChild.h
@@ -0,0 +1,318 @@
+/* -*- 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_DocAccessibleChild_h
+#define mozilla_a11y_DocAccessibleChild_h
+
+#include "mozilla/a11y/COMPtrTypes.h"
+#include "mozilla/a11y/DocAccessibleChildBase.h"
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/mscom/Ptr.h"
+
+namespace mozilla {
+namespace a11y {
+
+/*
+ * These objects handle content side communication for an accessible document,
+ * and their lifetime is the same as the document they represent.
+ */
+class DocAccessibleChild : public DocAccessibleChildBase
+{
+public:
+ explicit DocAccessibleChild(DocAccessible* aDoc);
+ ~DocAccessibleChild();
+
+ virtual void Shutdown() override;
+
+ virtual bool
+ RecvParentCOMProxy(const IAccessibleHolder& aParentCOMProxy) override;
+
+ IAccessible* GetParentIAccessible() const { return mParentProxy.get(); }
+
+ bool SendEvent(const uint64_t& aID, const uint32_t& type);
+ bool SendHideEvent(const uint64_t& aRootID, const bool& aFromUser);
+ bool SendStateChangeEvent(const uint64_t& aID, const uint64_t& aState,
+ const bool& aEnabled);
+ bool SendCaretMoveEvent(const uint64_t& aID, const int32_t& aOffset);
+ bool SendTextChangeEvent(const uint64_t& aID, const nsString& aStr,
+ const int32_t& aStart, const uint32_t& aLen,
+ const bool& aIsInsert, const bool& aFromUser);
+ bool SendSelectionEvent(const uint64_t& aID, const uint64_t& aWidgetID,
+ const uint32_t& aType);
+ bool SendRoleChangedEvent(const uint32_t& aRole);
+
+ bool ConstructChildDocInParentProcess(DocAccessibleChild* aNewChildDoc,
+ uint64_t aUniqueID, uint32_t aMsaaID);
+
+ bool SendBindChildDoc(DocAccessibleChild* aChildDoc,
+ const uint64_t& aNewParentID);
+
+protected:
+ virtual void MaybeSendShowEvent(ShowEventData& aData, bool aFromUser) override;
+
+private:
+ void RemoveDeferredConstructor();
+
+ bool IsConstructedInParentProcess() const { return mIsRemoteConstructed; }
+ void SetConstructedInParentProcess() { mIsRemoteConstructed = true; }
+
+ /**
+ * DocAccessibleChild should not fire events until it has asynchronously
+ * received the COM proxy for its parent. OTOH, content a11y may still be
+ * attempting to fire events during this window of time. If this object does
+ * not yet have its parent proxy, instead of immediately sending the events to
+ * our parent, we enqueue them to mDeferredEvents. As soon as
+ * RecvParentCOMProxy is called, we play back mDeferredEvents.
+ */
+ struct DeferredEvent
+ {
+ void Dispatch()
+ {
+ Dispatch(mTarget);
+ }
+
+ virtual ~DeferredEvent() {}
+
+ protected:
+ explicit DeferredEvent(DocAccessibleChild* aTarget)
+ : mTarget(aTarget)
+ {}
+
+ virtual void Dispatch(DocAccessibleChild* aIPCDoc) = 0;
+
+ private:
+ DocAccessibleChild* mTarget;
+ };
+
+ void PushDeferredEvent(UniquePtr<DeferredEvent> aEvent);
+
+ struct SerializedShow final : public DeferredEvent
+ {
+ SerializedShow(DocAccessibleChild* aTarget,
+ ShowEventData& aEventData, bool aFromUser)
+ : DeferredEvent(aTarget)
+ , mEventData(aEventData.ID(), aEventData.Idx(), nsTArray<AccessibleData>())
+ , mFromUser(aFromUser)
+ {
+ // Since IPDL doesn't generate a move constructor for ShowEventData,
+ // we move NewTree manually (ugh). We still construct with an empty
+ // NewTree above so that the compiler catches any changes made to the
+ // ShowEventData structure in IPDL.
+ mEventData.NewTree() = Move(aEventData.NewTree());
+ }
+
+ void Dispatch(DocAccessibleChild* aIPCDoc) override
+ {
+ Unused << aIPCDoc->SendShowEvent(mEventData, mFromUser);
+ }
+
+ ShowEventData mEventData;
+ bool mFromUser;
+ };
+
+ struct SerializedHide final : public DeferredEvent
+ {
+ SerializedHide(DocAccessibleChild* aTarget, uint64_t aRootID, bool aFromUser)
+ : DeferredEvent(aTarget)
+ , mRootID(aRootID)
+ , mFromUser(aFromUser)
+ {}
+
+ void Dispatch(DocAccessibleChild* aIPCDoc) override
+ {
+ Unused << aIPCDoc->SendHideEvent(mRootID, mFromUser);
+ }
+
+ uint64_t mRootID;
+ bool mFromUser;
+ };
+
+ struct SerializedStateChange final : public DeferredEvent
+ {
+ SerializedStateChange(DocAccessibleChild* aTarget, uint64_t aID,
+ uint64_t aState, bool aEnabled)
+ : DeferredEvent(aTarget)
+ , mID(aID)
+ , mState(aState)
+ , mEnabled(aEnabled)
+ {}
+
+ void Dispatch(DocAccessibleChild* aIPCDoc) override
+ {
+ Unused << aIPCDoc->SendStateChangeEvent(mID, mState, mEnabled);
+ }
+
+ uint64_t mID;
+ uint64_t mState;
+ bool mEnabled;
+ };
+
+ struct SerializedCaretMove final : public DeferredEvent
+ {
+ SerializedCaretMove(DocAccessibleChild* aTarget, uint64_t aID,
+ int32_t aOffset)
+ : DeferredEvent(aTarget)
+ , mID(aID)
+ , mOffset(aOffset)
+ {}
+
+ void Dispatch(DocAccessibleChild* aIPCDoc) override
+ {
+ Unused << aIPCDoc->SendCaretMoveEvent(mID, mOffset);
+ }
+
+ uint64_t mID;
+ int32_t mOffset;
+ };
+
+ struct SerializedTextChange final : public DeferredEvent
+ {
+ SerializedTextChange(DocAccessibleChild* aTarget, uint64_t aID,
+ const nsString& aStr, int32_t aStart, uint32_t aLen,
+ bool aIsInsert, bool aFromUser)
+ : DeferredEvent(aTarget)
+ , mID(aID)
+ , mStr(aStr)
+ , mStart(aStart)
+ , mLen(aLen)
+ , mIsInsert(aIsInsert)
+ , mFromUser(aFromUser)
+ {}
+
+ void Dispatch(DocAccessibleChild* aIPCDoc) override
+ {
+ Unused << aIPCDoc->SendTextChangeEvent(mID, mStr, mStart, mLen, mIsInsert,
+ mFromUser);
+ }
+
+ uint64_t mID;
+ nsString mStr;
+ int32_t mStart;
+ uint32_t mLen;
+ bool mIsInsert;
+ bool mFromUser;
+ };
+
+ struct SerializedSelection final : public DeferredEvent
+ {
+ SerializedSelection(DocAccessibleChild* aTarget, uint64_t aID,
+ uint64_t aWidgetID, uint32_t aType)
+ : DeferredEvent(aTarget)
+ , mID(aID)
+ , mWidgetID(aWidgetID)
+ , mType(aType)
+ {}
+
+ void Dispatch(DocAccessibleChild* aIPCDoc) override
+ {
+ Unused << aIPCDoc->SendSelectionEvent(mID, mWidgetID, mType);
+ }
+
+ uint64_t mID;
+ uint64_t mWidgetID;
+ uint32_t mType;
+ };
+
+ struct SerializedRoleChanged final : public DeferredEvent
+ {
+ explicit SerializedRoleChanged(DocAccessibleChild* aTarget, uint32_t aRole)
+ : DeferredEvent(aTarget)
+ , mRole(aRole)
+ {}
+
+ void Dispatch(DocAccessibleChild* aIPCDoc) override
+ {
+ Unused << aIPCDoc->SendRoleChangedEvent(mRole);
+ }
+
+ uint32_t mRole;
+ };
+
+ struct SerializedEvent final : public DeferredEvent
+ {
+ SerializedEvent(DocAccessibleChild* aTarget, uint64_t aID, uint32_t aType)
+ : DeferredEvent(aTarget)
+ , mID(aID)
+ , mType(aType)
+ {}
+
+ void Dispatch(DocAccessibleChild* aIPCDoc) override
+ {
+ Unused << aIPCDoc->SendEvent(mID, mType);
+ }
+
+ uint64_t mID;
+ uint32_t mType;
+ };
+
+ struct SerializedChildDocConstructor final : public DeferredEvent
+ {
+ SerializedChildDocConstructor(DocAccessibleChild* aIPCDoc,
+ DocAccessibleChild* aParentIPCDoc,
+ uint64_t aUniqueID, uint32_t aMsaaID)
+ : DeferredEvent(aParentIPCDoc)
+ , mIPCDoc(aIPCDoc)
+ , mUniqueID(aUniqueID)
+ , mMsaaID(aMsaaID)
+ {}
+
+ void Dispatch(DocAccessibleChild* aParentIPCDoc) override
+ {
+ auto tabChild = static_cast<dom::TabChild*>(aParentIPCDoc->Manager());
+ MOZ_ASSERT(tabChild);
+ Unused << tabChild->SendPDocAccessibleConstructor(mIPCDoc, aParentIPCDoc,
+ mUniqueID, mMsaaID,
+ IAccessibleHolder());
+ mIPCDoc->SetConstructedInParentProcess();
+ }
+
+ DocAccessibleChild* mIPCDoc;
+ uint64_t mUniqueID;
+ uint32_t mMsaaID;
+ };
+
+ friend struct SerializedChildDocConstructor;
+
+ struct SerializedBindChildDoc final : public DeferredEvent
+ {
+ SerializedBindChildDoc(DocAccessibleChild* aParentDoc,
+ DocAccessibleChild* aChildDoc, uint64_t aNewParentID)
+ : DeferredEvent(aParentDoc)
+ , mChildDoc(aChildDoc)
+ , mNewParentID(aNewParentID)
+ {}
+
+ void Dispatch(DocAccessibleChild* aParentIPCDoc) override
+ {
+ Unused << aParentIPCDoc->SendBindChildDoc(mChildDoc, mNewParentID);
+ }
+
+ DocAccessibleChild* mChildDoc;
+ uint64_t mNewParentID;
+ };
+
+ struct SerializedShutdown final : public DeferredEvent
+ {
+ explicit SerializedShutdown(DocAccessibleChild* aTarget)
+ : DeferredEvent(aTarget)
+ {
+ }
+
+ void Dispatch(DocAccessibleChild* aIPCDoc) override
+ {
+ aIPCDoc->Shutdown();
+ }
+ };
+
+ bool mIsRemoteConstructed;
+ mscom::ProxyUniquePtr<IAccessible> mParentProxy;
+ nsTArray<UniquePtr<DeferredEvent>> mDeferredEvents;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_DocAccessibleChild_h
diff --git a/accessible/ipc/win/PDocAccessible.ipdl b/accessible/ipc/win/PDocAccessible.ipdl
new file mode 100644
index 000000000..3389abd23
--- /dev/null
+++ b/accessible/ipc/win/PDocAccessible.ipdl
@@ -0,0 +1,75 @@
+/* -*- 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 protocol PFileDescriptorSet;
+include protocol PBrowser;
+
+using mozilla::a11y::IAccessibleHolder from "mozilla/a11y/IPCTypes.h";
+using mozilla::WindowsHandle from "ipc/IPCMessageUtils.h";
+
+namespace mozilla {
+namespace a11y {
+
+struct AccessibleData
+{
+ uint64_t ID;
+ int32_t MsaaID;
+ uint32_t Role;
+ uint32_t ChildrenCount;
+ uint32_t Interfaces;
+};
+
+struct ShowEventData
+{
+ uint64_t ID;
+ uint32_t Idx;
+ AccessibleData[] NewTree;
+};
+
+struct Attribute
+{
+ nsCString Name;
+ nsString Value;
+};
+
+sync protocol PDocAccessible
+{
+ manager PBrowser;
+
+parent:
+ async Shutdown();
+
+ /*
+ * Notify the parent process the document in the child process is firing an
+ * event.
+ */
+ async Event(uint64_t aID, uint32_t type);
+ async ShowEvent(ShowEventData data, bool aFromUser);
+ async HideEvent(uint64_t aRootID, bool aFromUser);
+ async StateChangeEvent(uint64_t aID, uint64_t aState, bool aEnabled);
+ async CaretMoveEvent(uint64_t aID, int32_t aOffset);
+ async TextChangeEvent(uint64_t aID, nsString aStr, int32_t aStart, uint32_t aLen,
+ bool aIsInsert, bool aFromUser);
+ async SelectionEvent(uint64_t aID, uint64_t aWidgetID, uint32_t aType);
+ async RoleChangedEvent(uint32_t aRole);
+
+ /*
+ * Tell the parent document to bind the existing document as a new child
+ * document.
+ */
+ async BindChildDoc(PDocAccessible aChildDoc, uint64_t aID);
+
+ sync GetWindowedPluginIAccessible(WindowsHandle aHwnd)
+ returns (IAccessibleHolder aPluginCOMProxy);
+
+child:
+ async ParentCOMProxy(IAccessibleHolder aParentCOMProxy);
+
+ async __delete__();
+};
+
+}
+}
diff --git a/accessible/ipc/win/PlatformChild.cpp b/accessible/ipc/win/PlatformChild.cpp
new file mode 100644
index 000000000..01434a081
--- /dev/null
+++ b/accessible/ipc/win/PlatformChild.cpp
@@ -0,0 +1,62 @@
+/* -*- 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 "mozilla/a11y/PlatformChild.h"
+#include "mozilla/mscom/EnsureMTA.h"
+#include "mozilla/mscom/InterceptorLog.h"
+
+#include "Accessible2.h"
+#include "Accessible2_2.h"
+#include "AccessibleHypertext2.h"
+#include "AccessibleTableCell.h"
+
+#include "AccessibleHypertext2_i.c"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Unfortunately the COM interceptor does not intrinsically handle array
+ * outparams. Instead we manually define the relevant metadata here, and
+ * register it in a call to mozilla::mscom::RegisterArrayData.
+ * @see mozilla::mscom::ArrayData
+ */
+static const mozilla::mscom::ArrayData sPlatformChildArrayData[] = {
+ {IID_IEnumVARIANT, 3, 1, VT_DISPATCH, IID_IDispatch, 2},
+ {IID_IAccessible2, 30, 1, VT_UNKNOWN | VT_BYREF, IID_IAccessibleRelation, 2},
+ {IID_IAccessibleRelation, 7, 1, VT_UNKNOWN | VT_BYREF, IID_IUnknown, 2},
+ {IID_IAccessible2_2, 48, 2, VT_UNKNOWN | VT_BYREF, IID_IUnknown, 3,
+ mozilla::mscom::ArrayData::Flag::eAllocatedByServer},
+ {IID_IAccessibleTableCell, 4, 0, VT_UNKNOWN | VT_BYREF, IID_IUnknown, 1,
+ mozilla::mscom::ArrayData::Flag::eAllocatedByServer},
+ {IID_IAccessibleTableCell, 7, 0, VT_UNKNOWN | VT_BYREF, IID_IUnknown, 1,
+ mozilla::mscom::ArrayData::Flag::eAllocatedByServer},
+ {IID_IAccessibleHypertext2, 25, 0, VT_UNKNOWN | VT_BYREF, IID_IUnknown, 1,
+ mozilla::mscom::ArrayData::Flag::eAllocatedByServer}
+};
+
+// Type libraries are thread-neutral, so we can register those from any
+// apartment. OTOH, proxies must be registered from within the apartment where
+// we intend to instantiate them. Therefore RegisterProxy() must be called
+// via EnsureMTA.
+PlatformChild::PlatformChild()
+ : mAccTypelib(mozilla::mscom::RegisterTypelib(L"oleacc.dll",
+ mozilla::mscom::RegistrationFlags::eUseSystemDirectory))
+ , mMiscTypelib(mozilla::mscom::RegisterTypelib(L"Accessible.tlb"))
+{
+ mozilla::mscom::InterceptorLog::Init();
+ mozilla::mscom::RegisterArrayData(sPlatformChildArrayData);
+
+ UniquePtr<mozilla::mscom::RegisteredProxy> ia2Proxy;
+ mozilla::mscom::EnsureMTA([&ia2Proxy]() -> void {
+ ia2Proxy = Move(mozilla::mscom::RegisterProxy(L"ia2marshal.dll"));
+ });
+ mIA2Proxy = Move(ia2Proxy);
+}
+
+} // namespace a11y
+} // namespace mozilla
+
diff --git a/accessible/ipc/win/PlatformChild.h b/accessible/ipc/win/PlatformChild.h
new file mode 100644
index 000000000..49daf161d
--- /dev/null
+++ b/accessible/ipc/win/PlatformChild.h
@@ -0,0 +1,35 @@
+/* -*- 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_PlatformChild_h
+#define mozilla_a11y_PlatformChild_h
+
+#include "mozilla/mscom/Registration.h"
+
+namespace mozilla {
+namespace a11y {
+
+class PlatformChild
+{
+public:
+ PlatformChild();
+
+ PlatformChild(PlatformChild&) = delete;
+ PlatformChild(PlatformChild&&) = delete;
+ PlatformChild& operator=(PlatformChild&) = delete;
+ PlatformChild& operator=(PlatformChild&&) = delete;
+
+private:
+ UniquePtr<mozilla::mscom::RegisteredProxy> mIA2Proxy;
+ UniquePtr<mozilla::mscom::RegisteredProxy> mAccTypelib;
+ UniquePtr<mozilla::mscom::RegisteredProxy> mMiscTypelib;
+};
+
+} // namespace mozilla
+} // namespace a11y
+
+#endif // mozilla_a11y_PlatformChild_h
+
diff --git a/accessible/ipc/win/ProxyAccessible.cpp b/accessible/ipc/win/ProxyAccessible.cpp
new file mode 100644
index 000000000..dcdf20ef9
--- /dev/null
+++ b/accessible/ipc/win/ProxyAccessible.cpp
@@ -0,0 +1,599 @@
+/* -*- 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 "Accessible2.h"
+#include "ProxyAccessible.h"
+#include "ia2AccessibleValue.h"
+#include "mozilla/a11y/DocAccessibleParent.h"
+#include "DocAccessible.h"
+#include "mozilla/a11y/DocManager.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/Unused.h"
+#include "mozilla/a11y/Platform.h"
+#include "RelationType.h"
+#include "mozilla/a11y/Role.h"
+#include "xpcAccessibleDocument.h"
+
+#include <comutil.h>
+
+static const VARIANT kChildIdSelf = {VT_I4};
+
+namespace mozilla {
+namespace a11y {
+
+bool
+ProxyAccessible::GetCOMInterface(void** aOutAccessible) const
+{
+ if (!aOutAccessible) {
+ return false;
+ }
+
+ if (!mCOMProxy) {
+ // See if we can lazily obtain a COM proxy
+ AccessibleWrap* wrap = WrapperFor(this);
+ bool isDefunct = false;
+ ProxyAccessible* thisPtr = const_cast<ProxyAccessible*>(this);
+ // NB: Don't pass CHILDID_SELF here, use the absolute MSAA ID. Otherwise
+ // GetIAccessibleFor will recurse into this function and we will just
+ // overflow the stack.
+ VARIANT realId = {VT_I4};
+ realId.ulVal = wrap->GetExistingID();
+ thisPtr->mCOMProxy = wrap->GetIAccessibleFor(realId, &isDefunct);
+ }
+
+ RefPtr<IAccessible> addRefed = mCOMProxy;
+ addRefed.forget(aOutAccessible);
+ return !!mCOMProxy;
+}
+
+/**
+ * Specializations of this template map an IAccessible type to its IID
+ */
+template<typename Interface> struct InterfaceIID {};
+
+template<>
+struct InterfaceIID<IAccessibleValue>
+{
+ static REFIID Value() { return IID_IAccessibleValue; }
+};
+
+template<>
+struct InterfaceIID<IAccessibleText>
+{
+ static REFIID Value() { return IID_IAccessibleText; }
+};
+
+/**
+ * Get the COM proxy for this proxy accessible and QueryInterface it with the
+ * correct IID
+ */
+template<typename Interface>
+static already_AddRefed<Interface>
+QueryInterface(const ProxyAccessible* aProxy)
+{
+ RefPtr<IAccessible> acc;
+ if (!aProxy->GetCOMInterface((void**)getter_AddRefs(acc))) {
+ return nullptr;
+ }
+
+ RefPtr<Interface> acc2;
+ if (FAILED(acc->QueryInterface(InterfaceIID<Interface>::Value(),
+ (void**)getter_AddRefs(acc2)))) {
+ return nullptr;
+ }
+
+ return acc2.forget();
+}
+
+void
+ProxyAccessible::Name(nsString& aName) const
+{
+ aName.Truncate();
+ RefPtr<IAccessible> acc;
+ if (!GetCOMInterface((void**)getter_AddRefs(acc))) {
+ return;
+ }
+
+ BSTR result;
+ HRESULT hr = acc->get_accName(kChildIdSelf, &result);
+ _bstr_t resultWrap(result, false);
+ if (FAILED(hr)) {
+ return;
+ }
+ aName = (wchar_t*)resultWrap;
+}
+
+void
+ProxyAccessible::Value(nsString& aValue) const
+{
+ aValue.Truncate();
+ RefPtr<IAccessible> acc;
+ if (!GetCOMInterface((void**)getter_AddRefs(acc))) {
+ return;
+ }
+
+ BSTR result;
+ HRESULT hr = acc->get_accValue(kChildIdSelf, &result);
+ _bstr_t resultWrap(result, false);
+ if (FAILED(hr)) {
+ return;
+ }
+ aValue = (wchar_t*)resultWrap;
+}
+
+void
+ProxyAccessible::Description(nsString& aDesc) const
+{
+ aDesc.Truncate();
+ RefPtr<IAccessible> acc;
+ if (!GetCOMInterface((void**)getter_AddRefs(acc))) {
+ return;
+ }
+
+ BSTR result;
+ HRESULT hr = acc->get_accDescription(kChildIdSelf, &result);
+ _bstr_t resultWrap(result, false);
+ if (FAILED(hr)) {
+ return;
+ }
+ aDesc = (wchar_t*)resultWrap;
+}
+
+uint64_t
+ProxyAccessible::State() const
+{
+ uint64_t state = 0;
+ RefPtr<IAccessible> acc;
+ if (!GetCOMInterface((void**)getter_AddRefs(acc))) {
+ return state;
+ }
+
+ VARIANT varState;
+ HRESULT hr = acc->get_accState(kChildIdSelf, &varState);
+ if (FAILED(hr)) {
+ return state;
+ }
+ return uint64_t(varState.lVal);
+}
+
+nsIntRect
+ProxyAccessible::Bounds()
+{
+ nsIntRect rect;
+
+ RefPtr<IAccessible> acc;
+ if (!GetCOMInterface((void**)getter_AddRefs(acc))) {
+ return rect;
+ }
+
+ long left;
+ long top;
+ long width;
+ long height;
+ HRESULT hr = acc->accLocation(&left, &top, &width, &height, kChildIdSelf);
+ if (FAILED(hr)) {
+ return rect;
+ }
+ rect.x = left;
+ rect.y = top;
+ rect.width = width;
+ rect.height = height;
+ return rect;
+}
+
+void
+ProxyAccessible::Language(nsString& aLocale)
+{
+ aLocale.Truncate();
+
+ RefPtr<IAccessible> acc;
+ if (!GetCOMInterface((void**)getter_AddRefs(acc))) {
+ return;
+ }
+
+ RefPtr<IAccessible2> acc2;
+ if (FAILED(acc->QueryInterface(IID_IAccessible2, (void**)getter_AddRefs(acc2)))) {
+ return;
+ }
+
+ IA2Locale locale;
+ HRESULT hr = acc2->get_locale(&locale);
+
+ _bstr_t langWrap(locale.language, false);
+ _bstr_t countryWrap(locale.country, false);
+ _bstr_t variantWrap(locale.variant, false);
+
+ if (FAILED(hr)) {
+ return;
+ }
+
+ // The remaining code should essentially be the inverse of the
+ // ia2Accessible::get_locale conversion to IA2Locale.
+
+ if (!!variantWrap) {
+ aLocale = (wchar_t*)variantWrap;
+ return;
+ }
+
+ if (!!langWrap) {
+ aLocale = (wchar_t*)langWrap;
+ if (!!countryWrap) {
+ aLocale += L"-";
+ aLocale += (wchar_t*)countryWrap;
+ }
+ }
+}
+
+static bool
+IsEscapedChar(const wchar_t c)
+{
+ return c == L'\\' || c == L':' || c == ',' || c == '=' || c == ';';
+}
+
+static bool
+ConvertBSTRAttributesToArray(const nsAString& aStr,
+ nsTArray<Attribute>* aAttrs)
+{
+ if (!aAttrs) {
+ return false;
+ }
+
+ enum
+ {
+ eName = 0,
+ eValue = 1,
+ eNumStates
+ } state;
+ nsAutoString tokens[eNumStates];
+ auto itr = aStr.BeginReading(), end = aStr.EndReading();
+
+ state = eName;
+ while (itr != end) {
+ switch (*itr) {
+ case L'\\':
+ // Skip the backslash so that we're looking at the escaped char
+ ++itr;
+ if (itr == end || !IsEscapedChar(*itr)) {
+ // Invalid state
+ return false;
+ }
+ break;
+ case L':':
+ if (state != eName) {
+ // Bad, should be looking at name
+ return false;
+ }
+ state = eValue;
+ ++itr;
+ continue;
+ case L';':
+ if (state != eValue) {
+ // Bad, should be looking at value
+ return false;
+ }
+ state = eName;
+ aAttrs->AppendElement(Attribute(NS_ConvertUTF16toUTF8(tokens[eName]),
+ tokens[eValue]));
+ tokens[eName].Truncate();
+ tokens[eValue].Truncate();
+ ++itr;
+ continue;
+ default:
+ break;
+ }
+ tokens[state] += *itr;
+ }
+ return true;
+}
+
+void
+ProxyAccessible::Attributes(nsTArray<Attribute>* aAttrs) const
+{
+ aAttrs->Clear();
+
+ RefPtr<IAccessible> acc;
+ if (!GetCOMInterface((void**)getter_AddRefs(acc))) {
+ return;
+ }
+
+ RefPtr<IAccessible2> acc2;
+ if (FAILED(acc->QueryInterface(IID_IAccessible2, (void**)getter_AddRefs(acc2)))) {
+ return;
+ }
+
+ BSTR attrs;
+ HRESULT hr = acc2->get_attributes(&attrs);
+ _bstr_t attrsWrap(attrs, false);
+ if (FAILED(hr)) {
+ return;
+ }
+
+ ConvertBSTRAttributesToArray(nsDependentString((wchar_t*)attrs,
+ attrsWrap.length()),
+ aAttrs);
+}
+
+double
+ProxyAccessible::CurValue()
+{
+ RefPtr<IAccessibleValue> acc = QueryInterface<IAccessibleValue>(this);
+ if (!acc) {
+ return UnspecifiedNaN<double>();
+ }
+
+ VARIANT currentValue;
+ HRESULT hr = acc->get_currentValue(&currentValue);
+ if (FAILED(hr) || currentValue.vt != VT_R8) {
+ return UnspecifiedNaN<double>();
+ }
+
+ return currentValue.dblVal;
+}
+
+bool
+ProxyAccessible::SetCurValue(double aValue)
+{
+ RefPtr<IAccessibleValue> acc = QueryInterface<IAccessibleValue>(this);
+ if (!acc) {
+ return false;
+ }
+
+ VARIANT currentValue;
+ VariantInit(&currentValue);
+ currentValue.vt = VT_R8;
+ currentValue.dblVal = aValue;
+ HRESULT hr = acc->setCurrentValue(currentValue);
+ return SUCCEEDED(hr);
+}
+
+double
+ProxyAccessible::MinValue()
+{
+ RefPtr<IAccessibleValue> acc = QueryInterface<IAccessibleValue>(this);
+ if (!acc) {
+ return UnspecifiedNaN<double>();
+ }
+
+ VARIANT minimumValue;
+ HRESULT hr = acc->get_minimumValue(&minimumValue);
+ if (FAILED(hr) || minimumValue.vt != VT_R8) {
+ return UnspecifiedNaN<double>();
+ }
+
+ return minimumValue.dblVal;
+}
+
+double
+ProxyAccessible::MaxValue()
+{
+ RefPtr<IAccessibleValue> acc = QueryInterface<IAccessibleValue>(this);
+ if (!acc) {
+ return UnspecifiedNaN<double>();
+ }
+
+ VARIANT maximumValue;
+ HRESULT hr = acc->get_maximumValue(&maximumValue);
+ if (FAILED(hr) || maximumValue.vt != VT_R8) {
+ return UnspecifiedNaN<double>();
+ }
+
+ return maximumValue.dblVal;
+}
+
+static IA2TextBoundaryType
+GetIA2TextBoundary(AccessibleTextBoundary aGeckoBoundaryType)
+{
+ switch (aGeckoBoundaryType) {
+ case nsIAccessibleText::BOUNDARY_CHAR:
+ return IA2_TEXT_BOUNDARY_CHAR;
+ case nsIAccessibleText::BOUNDARY_WORD_START:
+ return IA2_TEXT_BOUNDARY_WORD;
+ case nsIAccessibleText::BOUNDARY_LINE_START:
+ return IA2_TEXT_BOUNDARY_LINE;
+ default:
+ MOZ_RELEASE_ASSERT(false);
+ }
+}
+
+bool
+ProxyAccessible::TextSubstring(int32_t aStartOffset, int32_t aEndOffset,
+ nsString& aText) const
+{
+ RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this);
+ if (!acc) {
+ return false;
+ }
+
+ BSTR result;
+ HRESULT hr = acc->get_text(static_cast<long>(aStartOffset),
+ static_cast<long>(aEndOffset), &result);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ _bstr_t resultWrap(result, false);
+ aText = (wchar_t*)result;
+
+ return true;
+}
+
+void
+ProxyAccessible::GetTextBeforeOffset(int32_t aOffset,
+ AccessibleTextBoundary aBoundaryType,
+ nsString& aText, int32_t* aStartOffset,
+ int32_t* aEndOffset)
+{
+ RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this);
+ if (!acc) {
+ return;
+ }
+
+ BSTR result;
+ long start, end;
+ HRESULT hr = acc->get_textBeforeOffset(aOffset,
+ GetIA2TextBoundary(aBoundaryType),
+ &start, &end, &result);
+ if (FAILED(hr)) {
+ return;
+ }
+
+ _bstr_t resultWrap(result, false);
+ *aStartOffset = start;
+ *aEndOffset = end;
+ aText = (wchar_t*)result;
+}
+
+void
+ProxyAccessible::GetTextAfterOffset(int32_t aOffset,
+ AccessibleTextBoundary aBoundaryType,
+ nsString& aText, int32_t* aStartOffset,
+ int32_t* aEndOffset)
+{
+ RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this);
+ if (!acc) {
+ return;
+ }
+
+ BSTR result;
+ long start, end;
+ HRESULT hr = acc->get_textAfterOffset(aOffset,
+ GetIA2TextBoundary(aBoundaryType),
+ &start, &end, &result);
+ if (FAILED(hr)) {
+ return;
+ }
+
+ _bstr_t resultWrap(result, false);
+ aText = (wchar_t*)result;
+ *aStartOffset = start;
+ *aEndOffset = end;
+}
+
+void
+ProxyAccessible::GetTextAtOffset(int32_t aOffset,
+ AccessibleTextBoundary aBoundaryType,
+ nsString& aText, int32_t* aStartOffset,
+ int32_t* aEndOffset)
+{
+ RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this);
+ if (!acc) {
+ return;
+ }
+
+ BSTR result;
+ long start, end;
+ HRESULT hr = acc->get_textAtOffset(aOffset, GetIA2TextBoundary(aBoundaryType),
+ &start, &end, &result);
+ if (FAILED(hr)) {
+ return;
+ }
+
+ _bstr_t resultWrap(result, false);
+ aText = (wchar_t*)result;
+ *aStartOffset = start;
+ *aEndOffset = end;
+}
+
+bool
+ProxyAccessible::AddToSelection(int32_t aStartOffset, int32_t aEndOffset)
+{
+ RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this);
+ if (!acc) {
+ return false;
+ }
+
+ return SUCCEEDED(acc->addSelection(static_cast<long>(aStartOffset),
+ static_cast<long>(aEndOffset)));
+}
+
+bool
+ProxyAccessible::RemoveFromSelection(int32_t aSelectionNum)
+{
+ RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this);
+ if (!acc) {
+ return false;
+ }
+
+ return SUCCEEDED(acc->removeSelection(static_cast<long>(aSelectionNum)));
+}
+
+int32_t
+ProxyAccessible::CaretOffset()
+{
+ RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this);
+ if (!acc) {
+ return -1;
+ }
+
+ long offset;
+ HRESULT hr = acc->get_caretOffset(&offset);
+ if (FAILED(hr)) {
+ return -1;
+ }
+
+ return static_cast<int32_t>(offset);
+}
+
+void
+ProxyAccessible::SetCaretOffset(int32_t aOffset)
+{
+ RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this);
+ if (!acc) {
+ return;
+ }
+
+ acc->setCaretOffset(static_cast<long>(aOffset));
+}
+
+/**
+ * aScrollType should be one of the nsIAccessiblescrollType constants.
+ */
+void
+ProxyAccessible::ScrollSubstringTo(int32_t aStartOffset, int32_t aEndOffset,
+ uint32_t aScrollType)
+{
+ RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this);
+ if (!acc) {
+ return;
+ }
+
+ acc->scrollSubstringTo(static_cast<long>(aStartOffset),
+ static_cast<long>(aEndOffset),
+ static_cast<IA2ScrollType>(aScrollType));
+}
+
+/**
+ * aCoordinateType is one of the nsIAccessibleCoordinateType constants.
+ */
+void
+ProxyAccessible::ScrollSubstringToPoint(int32_t aStartOffset, int32_t aEndOffset,
+ uint32_t aCoordinateType, int32_t aX,
+ int32_t aY)
+{
+ RefPtr<IAccessibleText> acc = QueryInterface<IAccessibleText>(this);
+ if (!acc) {
+ return;
+ }
+
+ IA2CoordinateType coordType;
+ if (aCoordinateType == nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE) {
+ coordType = IA2_COORDTYPE_SCREEN_RELATIVE;
+ } else if (aCoordinateType == nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE) {
+ coordType = IA2_COORDTYPE_PARENT_RELATIVE;
+ } else {
+ MOZ_RELEASE_ASSERT(false, "unsupported coord type");
+ }
+
+ acc->scrollSubstringToPoint(static_cast<long>(aStartOffset),
+ static_cast<long>(aEndOffset),
+ coordType,
+ static_cast<long>(aX),
+ static_cast<long>(aY));
+}
+
+} // namespace a11y
+} // namespace mozilla
diff --git a/accessible/ipc/win/ProxyAccessible.h b/accessible/ipc/win/ProxyAccessible.h
new file mode 100644
index 000000000..c8e5a43b1
--- /dev/null
+++ b/accessible/ipc/win/ProxyAccessible.h
@@ -0,0 +1,58 @@
+/* -*- 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_ProxyAccessible_h
+#define mozilla_a11y_ProxyAccessible_h
+
+#include "Accessible.h"
+#include "mozilla/a11y/ProxyAccessibleBase.h"
+#include "mozilla/a11y/Role.h"
+#include "nsIAccessibleText.h"
+#include "nsIAccessibleTypes.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsRect.h"
+
+#include <oleacc.h>
+
+namespace mozilla {
+namespace a11y {
+
+class ProxyAccessible : public ProxyAccessibleBase<ProxyAccessible>
+{
+public:
+ ProxyAccessible(uint64_t aID, ProxyAccessible* aParent,
+ DocAccessibleParent* aDoc, role aRole, uint32_t aInterfaces)
+ : ProxyAccessibleBase(aID, aParent, aDoc, aRole, aInterfaces)
+ {
+ MOZ_COUNT_CTOR(ProxyAccessible);
+ }
+
+ ~ProxyAccessible()
+ {
+ MOZ_COUNT_DTOR(ProxyAccessible);
+ }
+
+#include "mozilla/a11y/ProxyAccessibleShared.h"
+
+ bool GetCOMInterface(void** aOutAccessible) const;
+
+protected:
+ explicit ProxyAccessible(DocAccessibleParent* aThisAsDoc)
+ : ProxyAccessibleBase(aThisAsDoc)
+ { MOZ_COUNT_CTOR(ProxyAccessible); }
+
+ void SetCOMInterface(const RefPtr<IAccessible>& aIAccessible)
+ { mCOMProxy = aIAccessible; }
+
+private:
+ RefPtr<IAccessible> mCOMProxy;
+};
+
+}
+}
+
+#endif
diff --git a/accessible/ipc/win/moz.build b/accessible/ipc/win/moz.build
new file mode 100644
index 000000000..4bbcec417
--- /dev/null
+++ b/accessible/ipc/win/moz.build
@@ -0,0 +1,39 @@
+# -*- Mode: python; c-basic-offset: 4; 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/.
+
+if CONFIG['COMPILE_ENVIRONMENT']:
+ DIRS += ['typelib']
+
+# With --disable-accessibility, we need to compile PDocAccessible.ipdl (which
+# also depends on COMPtrTypes.h), but not the C++.
+IPDL_SOURCES += ['PDocAccessible.ipdl']
+EXPORTS.mozilla.a11y += ['COMPtrTypes.h']
+
+if CONFIG['ACCESSIBILITY']:
+ EXPORTS.mozilla.a11y += [
+ 'DocAccessibleChild.h',
+ 'PlatformChild.h',
+ 'ProxyAccessible.h'
+ ]
+
+ SOURCES += [
+ 'COMPtrTypes.cpp',
+ 'DocAccessibleChild.cpp',
+ 'PlatformChild.cpp',
+ 'ProxyAccessible.cpp',
+ ]
+
+ LOCAL_INCLUDES += [
+ '/accessible/base',
+ '/accessible/generic',
+ '/accessible/windows/ia2',
+ '/accessible/windows/msaa',
+ '/accessible/xpcom',
+ ]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
diff --git a/accessible/ipc/win/typelib/Accessible.idl b/accessible/ipc/win/typelib/Accessible.idl
new file mode 100644
index 000000000..82ddcf506
--- /dev/null
+++ b/accessible/ipc/win/typelib/Accessible.idl
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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/. */
+
+import "oaidl.idl";
+import "servprov.idl";
+
+[uuid(b4d37cda-0dac-45e6-b613-158a5eb94293)]
+library Accessible
+{
+ interface IEnumVARIANT;
+ interface IServiceProvider;
+};
+
diff --git a/accessible/ipc/win/typelib/Makefile.in b/accessible/ipc/win/typelib/Makefile.in
new file mode 100644
index 000000000..78e0cea29
--- /dev/null
+++ b/accessible/ipc/win/typelib/Makefile.in
@@ -0,0 +1,31 @@
+# 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/.
+
+GARBAGE += $(MIDL_GENERATED_FILES) done_gen dlldata.c
+
+MIDL_GENERATED_FILES = \
+ Accessible.h \
+ Accessible_i.c \
+ Accessible_p.c \
+ Accessible.tlb \
+ $(NULL)
+
+$(MIDL_GENERATED_FILES): done_gen
+
+done_gen: Accessible.idl
+ $(MIDL) $(MIDL_FLAGS) -Oicf $(srcdir)/Accessible.idl
+ touch $@
+
+export:: done_gen
+
+midl_exports := \
+ Accessible.tlb \
+ $(NULL)
+
+INSTALL_TARGETS += midl_exports
+midl_exports_FILES := $(midl_exports)
+midl_exports_DEST = $(DIST)/bin
+midl_exports_TARGET := export
+
+include $(topsrcdir)/config/rules.mk
diff --git a/accessible/ipc/win/typelib/moz.build b/accessible/ipc/win/typelib/moz.build
new file mode 100644
index 000000000..3bc45a136
--- /dev/null
+++ b/accessible/ipc/win/typelib/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; c-basic-offset: 4; 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/.
+
+FINAL_TARGET_FILES += [
+ '!Accessible.tlb',
+]
+
+GENERATED_FILES += [
+ 'Accessible.tlb',
+]
diff --git a/accessible/jsat/AccessFu.css b/accessible/jsat/AccessFu.css
new file mode 100644
index 000000000..d3930ff09
--- /dev/null
+++ b/accessible/jsat/AccessFu.css
@@ -0,0 +1,59 @@
+/* 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/. */
+
+#virtual-cursor-box {
+ position: fixed;
+ border: 1px solid orange;
+ pointer-events: none;
+ display: none;
+ border-radius: 2px;
+ box-shadow: 1px 1px 1px #444;
+ display: none;
+ z-index: 10;
+}
+
+#virtual-cursor-box.show {
+ display: block;
+}
+
+#virtual-cursor-box > div {
+ border-radius: 1px;
+ box-shadow: inset 1px 1px 1px #444;
+ display: block;
+ box-sizing: border-box;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+}
+
+#announce-box {
+ position: fixed;
+ width: 7.5em;
+ height: 5em;
+ top: calc(100% - 50% - 2.5em);
+ left: calc(100% - 50% - 3.75em);
+ pointer-events: none;
+ display: table;
+ font-size: 28pt;
+ font-weight: 700;
+ color: orange;
+ background-color: black;
+ border-radius: 0.25em;
+}
+
+#announce-box:not(.showing) {
+ opacity: 0.0;
+ -moz-transition: opacity 0.4s linear;
+}
+
+#announce-box.showing {
+ opacity: 1.0;
+ -moz-transition: opacity 0.2s linear;
+}
+
+#announce-box * {
+ text-align: center;
+ display: table-cell;
+ vertical-align: middle;
+}
diff --git a/accessible/jsat/AccessFu.jsm b/accessible/jsat/AccessFu.jsm
new file mode 100644
index 000000000..c6b16b38f
--- /dev/null
+++ b/accessible/jsat/AccessFu.jsm
@@ -0,0 +1,1000 @@
+/* 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/. */
+
+/* global AccessFu, Components, Utils, PrefCache, Logger, Services,
+ PointerAdapter, dump, Presentation, Rect */
+/* exported AccessFu */
+
+'use strict';
+
+const {utils: Cu, interfaces: Ci} = Components;
+
+this.EXPORTED_SYMBOLS = ['AccessFu']; // jshint ignore:line
+
+Cu.import('resource://gre/modules/Services.jsm');
+Cu.import('resource://gre/modules/accessibility/Utils.jsm');
+
+const ACCESSFU_DISABLE = 0; // jshint ignore:line
+const ACCESSFU_ENABLE = 1;
+const ACCESSFU_AUTO = 2;
+
+const SCREENREADER_SETTING = 'accessibility.screenreader';
+const QUICKNAV_MODES_PREF = 'accessibility.accessfu.quicknav_modes';
+const QUICKNAV_INDEX_PREF = 'accessibility.accessfu.quicknav_index';
+
+this.AccessFu = { // jshint ignore:line
+ /**
+ * Initialize chrome-layer accessibility functionality.
+ * If accessibility is enabled on the platform, then a special accessibility
+ * mode is started.
+ */
+ attach: function attach(aWindow) {
+ Utils.init(aWindow);
+
+ try {
+ Services.androidBridge.handleGeckoMessage(
+ { type: 'Accessibility:Ready' });
+ Services.obs.addObserver(this, 'Accessibility:Settings', false);
+ } catch (x) {
+ // Not on Android
+ if (aWindow.navigator.mozSettings) {
+ let lock = aWindow.navigator.mozSettings.createLock();
+ let req = lock.get(SCREENREADER_SETTING);
+ req.addEventListener('success', () => {
+ this._systemPref = req.result[SCREENREADER_SETTING];
+ this._enableOrDisable();
+ });
+ aWindow.navigator.mozSettings.addObserver(
+ SCREENREADER_SETTING, this.handleEvent);
+ }
+ }
+
+ this._activatePref = new PrefCache(
+ 'accessibility.accessfu.activate', this._enableOrDisable.bind(this));
+
+ this._enableOrDisable();
+ },
+
+ /**
+ * Shut down chrome-layer accessibility functionality from the outside.
+ */
+ detach: function detach() {
+ // Avoid disabling twice.
+ if (this._enabled) {
+ this._disable();
+ }
+ if (Utils.MozBuildApp === 'mobile/android') {
+ Services.obs.removeObserver(this, 'Accessibility:Settings');
+ } else if (Utils.win.navigator.mozSettings) {
+ Utils.win.navigator.mozSettings.removeObserver(
+ SCREENREADER_SETTING, this.handleEvent);
+ }
+ delete this._activatePref;
+ Utils.uninit();
+ },
+
+ /**
+ * A lazy getter for event handler that binds the scope to AccessFu object.
+ */
+ get handleEvent() {
+ delete this.handleEvent;
+ this.handleEvent = this._handleEvent.bind(this);
+ return this.handleEvent;
+ },
+
+ /**
+ * Start AccessFu mode, this primarily means controlling the virtual cursor
+ * with arrow keys.
+ */
+ _enable: function _enable() {
+ if (this._enabled) {
+ return;
+ }
+ this._enabled = true;
+
+ Cu.import('resource://gre/modules/accessibility/Utils.jsm');
+ Cu.import('resource://gre/modules/accessibility/PointerAdapter.jsm');
+ Cu.import('resource://gre/modules/accessibility/Presentation.jsm');
+
+ for (let mm of Utils.AllMessageManagers) {
+ this._addMessageListeners(mm);
+ this._loadFrameScript(mm);
+ }
+
+ // Add stylesheet
+ let stylesheetURL = 'chrome://global/content/accessibility/AccessFu.css';
+ let stylesheet = Utils.win.document.createProcessingInstruction(
+ 'xml-stylesheet', 'href="' + stylesheetURL + '" type="text/css"');
+ Utils.win.document.insertBefore(stylesheet, Utils.win.document.firstChild);
+ this.stylesheet = Cu.getWeakReference(stylesheet);
+
+
+ // Populate quicknav modes
+ this._quicknavModesPref =
+ new PrefCache(QUICKNAV_MODES_PREF, (aName, aValue, aFirstRun) => {
+ this.Input.quickNavMode.updateModes(aValue);
+ if (!aFirstRun) {
+ // If the modes change, reset the current mode index to 0.
+ Services.prefs.setIntPref(QUICKNAV_INDEX_PREF, 0);
+ }
+ }, true);
+
+ this._quicknavCurrentModePref =
+ new PrefCache(QUICKNAV_INDEX_PREF, (aName, aValue) => {
+ this.Input.quickNavMode.updateCurrentMode(Number(aValue));
+ }, true);
+
+ // Check for output notification
+ this._notifyOutputPref =
+ new PrefCache('accessibility.accessfu.notify_output');
+
+
+ this.Input.start();
+ Output.start();
+ PointerAdapter.start();
+
+ Services.obs.addObserver(this, 'remote-browser-shown', false);
+ Services.obs.addObserver(this, 'inprocess-browser-shown', false);
+ Services.obs.addObserver(this, 'Accessibility:NextObject', false);
+ Services.obs.addObserver(this, 'Accessibility:PreviousObject', false);
+ Services.obs.addObserver(this, 'Accessibility:Focus', false);
+ Services.obs.addObserver(this, 'Accessibility:ActivateObject', false);
+ Services.obs.addObserver(this, 'Accessibility:LongPress', false);
+ Services.obs.addObserver(this, 'Accessibility:ScrollForward', false);
+ Services.obs.addObserver(this, 'Accessibility:ScrollBackward', false);
+ Services.obs.addObserver(this, 'Accessibility:MoveByGranularity', false);
+ Utils.win.addEventListener('TabOpen', this);
+ Utils.win.addEventListener('TabClose', this);
+ Utils.win.addEventListener('TabSelect', this);
+
+ if (this.readyCallback) {
+ this.readyCallback();
+ delete this.readyCallback;
+ }
+
+ Logger.info('AccessFu:Enabled');
+ },
+
+ /**
+ * Disable AccessFu and return to default interaction mode.
+ */
+ _disable: function _disable() {
+ if (!this._enabled) {
+ return;
+ }
+
+ this._enabled = false;
+
+ Utils.win.document.removeChild(this.stylesheet.get());
+
+ for (let mm of Utils.AllMessageManagers) {
+ mm.sendAsyncMessage('AccessFu:Stop');
+ this._removeMessageListeners(mm);
+ }
+
+ this.Input.stop();
+ Output.stop();
+ PointerAdapter.stop();
+
+ Utils.win.removeEventListener('TabOpen', this);
+ Utils.win.removeEventListener('TabClose', this);
+ Utils.win.removeEventListener('TabSelect', this);
+
+ Services.obs.removeObserver(this, 'remote-browser-shown');
+ Services.obs.removeObserver(this, 'inprocess-browser-shown');
+ Services.obs.removeObserver(this, 'Accessibility:NextObject');
+ Services.obs.removeObserver(this, 'Accessibility:PreviousObject');
+ Services.obs.removeObserver(this, 'Accessibility:Focus');
+ Services.obs.removeObserver(this, 'Accessibility:ActivateObject');
+ Services.obs.removeObserver(this, 'Accessibility:LongPress');
+ Services.obs.removeObserver(this, 'Accessibility:ScrollForward');
+ Services.obs.removeObserver(this, 'Accessibility:ScrollBackward');
+ Services.obs.removeObserver(this, 'Accessibility:MoveByGranularity');
+
+ delete this._quicknavModesPref;
+ delete this._notifyOutputPref;
+
+ if (this.doneCallback) {
+ this.doneCallback();
+ delete this.doneCallback;
+ }
+
+ Logger.info('AccessFu:Disabled');
+ },
+
+ _enableOrDisable: function _enableOrDisable() {
+ try {
+ if (!this._activatePref) {
+ return;
+ }
+ let activatePref = this._activatePref.value;
+ if (activatePref == ACCESSFU_ENABLE ||
+ this._systemPref && activatePref == ACCESSFU_AUTO) {
+ this._enable();
+ } else {
+ this._disable();
+ }
+ } catch (x) {
+ dump('Error ' + x.message + ' ' + x.fileName + ':' + x.lineNumber);
+ }
+ },
+
+ receiveMessage: function receiveMessage(aMessage) {
+ Logger.debug(() => {
+ return ['Recieved', aMessage.name, JSON.stringify(aMessage.json)];
+ });
+
+ switch (aMessage.name) {
+ case 'AccessFu:Ready':
+ let mm = Utils.getMessageManager(aMessage.target);
+ if (this._enabled) {
+ mm.sendAsyncMessage('AccessFu:Start',
+ {method: 'start', buildApp: Utils.MozBuildApp});
+ }
+ break;
+ case 'AccessFu:Present':
+ this._output(aMessage.json, aMessage.target);
+ break;
+ case 'AccessFu:Input':
+ this.Input.setEditState(aMessage.json);
+ break;
+ case 'AccessFu:DoScroll':
+ this.Input.doScroll(aMessage.json);
+ break;
+ }
+ },
+
+ _output: function _output(aPresentationData, aBrowser) {
+ if (!Utils.isAliveAndVisible(
+ Utils.AccService.getAccessibleFor(aBrowser))) {
+ return;
+ }
+ for (let presenter of aPresentationData) {
+ if (!presenter) {
+ continue;
+ }
+
+ try {
+ Output[presenter.type](presenter.details, aBrowser);
+ } catch (x) {
+ Logger.logException(x);
+ }
+ }
+
+ if (this._notifyOutputPref.value) {
+ Services.obs.notifyObservers(null, 'accessibility-output',
+ JSON.stringify(aPresentationData));
+ }
+ },
+
+ _loadFrameScript: function _loadFrameScript(aMessageManager) {
+ if (this._processedMessageManagers.indexOf(aMessageManager) < 0) {
+ aMessageManager.loadFrameScript(
+ 'chrome://global/content/accessibility/content-script.js', true);
+ this._processedMessageManagers.push(aMessageManager);
+ } else if (this._enabled) {
+ // If the content-script is already loaded and AccessFu is enabled,
+ // send an AccessFu:Start message.
+ aMessageManager.sendAsyncMessage('AccessFu:Start',
+ {method: 'start', buildApp: Utils.MozBuildApp});
+ }
+ },
+
+ _addMessageListeners: function _addMessageListeners(aMessageManager) {
+ aMessageManager.addMessageListener('AccessFu:Present', this);
+ aMessageManager.addMessageListener('AccessFu:Input', this);
+ aMessageManager.addMessageListener('AccessFu:Ready', this);
+ aMessageManager.addMessageListener('AccessFu:DoScroll', this);
+ },
+
+ _removeMessageListeners: function _removeMessageListeners(aMessageManager) {
+ aMessageManager.removeMessageListener('AccessFu:Present', this);
+ aMessageManager.removeMessageListener('AccessFu:Input', this);
+ aMessageManager.removeMessageListener('AccessFu:Ready', this);
+ aMessageManager.removeMessageListener('AccessFu:DoScroll', this);
+ },
+
+ _handleMessageManager: function _handleMessageManager(aMessageManager) {
+ if (this._enabled) {
+ this._addMessageListeners(aMessageManager);
+ }
+ this._loadFrameScript(aMessageManager);
+ },
+
+ observe: function observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case 'Accessibility:Settings':
+ this._systemPref = JSON.parse(aData).enabled;
+ this._enableOrDisable();
+ break;
+ case 'Accessibility:NextObject':
+ case 'Accessibility:PreviousObject':
+ {
+ let rule = aData ?
+ aData.substr(0, 1).toUpperCase() + aData.substr(1).toLowerCase() :
+ 'Simple';
+ let method = aTopic.replace(/Accessibility:(\w+)Object/, 'move$1');
+ this.Input.moveCursor(method, rule, 'gesture');
+ break;
+ }
+ case 'Accessibility:ActivateObject':
+ this.Input.activateCurrent(JSON.parse(aData));
+ break;
+ case 'Accessibility:LongPress':
+ this.Input.sendContextMenuMessage();
+ break;
+ case 'Accessibility:ScrollForward':
+ this.Input.androidScroll('forward');
+ break;
+ case 'Accessibility:ScrollBackward':
+ this.Input.androidScroll('backward');
+ break;
+ case 'Accessibility:Focus':
+ this._focused = JSON.parse(aData);
+ if (this._focused) {
+ this.autoMove({ forcePresent: true, noOpIfOnScreen: true });
+ }
+ break;
+ case 'Accessibility:MoveByGranularity':
+ this.Input.moveByGranularity(JSON.parse(aData));
+ break;
+ case 'remote-browser-shown':
+ case 'inprocess-browser-shown':
+ {
+ // Ignore notifications that aren't from a BrowserOrApp
+ let frameLoader = aSubject.QueryInterface(Ci.nsIFrameLoader);
+ if (!frameLoader.ownerIsMozBrowserOrAppFrame) {
+ return;
+ }
+ this._handleMessageManager(frameLoader.messageManager);
+ break;
+ }
+ }
+ },
+
+ _handleEvent: function _handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case 'TabOpen':
+ {
+ let mm = Utils.getMessageManager(aEvent.target);
+ this._handleMessageManager(mm);
+ break;
+ }
+ case 'TabClose':
+ {
+ let mm = Utils.getMessageManager(aEvent.target);
+ let mmIndex = this._processedMessageManagers.indexOf(mm);
+ if (mmIndex > -1) {
+ this._removeMessageListeners(mm);
+ this._processedMessageManagers.splice(mmIndex, 1);
+ }
+ break;
+ }
+ case 'TabSelect':
+ {
+ if (this._focused) {
+ // We delay this for half a second so the awesomebar could close,
+ // and we could use the current coordinates for the content item.
+ // XXX TODO figure out how to avoid magic wait here.
+ this.autoMove({
+ delay: 500,
+ forcePresent: true,
+ noOpIfOnScreen: true,
+ moveMethod: 'moveFirst' });
+ }
+ break;
+ }
+ default:
+ {
+ // A settings change, it does not have an event type
+ if (aEvent.settingName == SCREENREADER_SETTING) {
+ this._systemPref = aEvent.settingValue;
+ this._enableOrDisable();
+ }
+ break;
+ }
+ }
+ },
+
+ autoMove: function autoMove(aOptions) {
+ let mm = Utils.getMessageManager(Utils.CurrentBrowser);
+ mm.sendAsyncMessage('AccessFu:AutoMove', aOptions);
+ },
+
+ announce: function announce(aAnnouncement) {
+ this._output(Presentation.announce(aAnnouncement), Utils.CurrentBrowser);
+ },
+
+ // So we don't enable/disable twice
+ _enabled: false,
+
+ // Layerview is focused
+ _focused: false,
+
+ // Keep track of message managers tha already have a 'content-script.js'
+ // injected.
+ _processedMessageManagers: [],
+
+ /**
+ * Adjusts the given bounds relative to the given browser.
+ * @param {Rect} aJsonBounds the bounds to adjust
+ * @param {browser} aBrowser the browser we want the bounds relative to
+ * @param {bool} aToCSSPixels whether to convert to CSS pixels (as opposed to
+ * device pixels)
+ */
+ adjustContentBounds:
+ function(aJsonBounds, aBrowser, aToCSSPixels) {
+ let bounds = new Rect(aJsonBounds.left, aJsonBounds.top,
+ aJsonBounds.right - aJsonBounds.left,
+ aJsonBounds.bottom - aJsonBounds.top);
+ let win = Utils.win;
+ let dpr = win.devicePixelRatio;
+ let offset = { left: -win.mozInnerScreenX, top: -win.mozInnerScreenY };
+
+ // Add the offset; the offset is in CSS pixels, so multiply the
+ // devicePixelRatio back in before adding to preserve unit consistency.
+ bounds = bounds.translate(offset.left * dpr, offset.top * dpr);
+
+ // If we want to get to CSS pixels from device pixels, this needs to be
+ // further divided by the devicePixelRatio due to widget scaling.
+ if (aToCSSPixels) {
+ bounds = bounds.scale(1 / dpr, 1 / dpr);
+ }
+
+ return bounds.expandToIntegers();
+ }
+};
+
+var Output = {
+ brailleState: {
+ startOffset: 0,
+ endOffset: 0,
+ text: '',
+ selectionStart: 0,
+ selectionEnd: 0,
+
+ init: function init(aOutput) {
+ if (aOutput && 'output' in aOutput) {
+ this.startOffset = aOutput.startOffset;
+ this.endOffset = aOutput.endOffset;
+ // We need to append a space at the end so that the routing key
+ // corresponding to the end of the output (i.e. the space) can be hit to
+ // move the caret there.
+ this.text = aOutput.output + ' ';
+ this.selectionStart = typeof aOutput.selectionStart === 'number' ?
+ aOutput.selectionStart : this.selectionStart;
+ this.selectionEnd = typeof aOutput.selectionEnd === 'number' ?
+ aOutput.selectionEnd : this.selectionEnd;
+
+ return { text: this.text,
+ selectionStart: this.selectionStart,
+ selectionEnd: this.selectionEnd };
+ }
+
+ return null;
+ },
+
+ adjustText: function adjustText(aText) {
+ let newBraille = [];
+ let braille = {};
+
+ let prefix = this.text.substring(0, this.startOffset).trim();
+ if (prefix) {
+ prefix += ' ';
+ newBraille.push(prefix);
+ }
+
+ newBraille.push(aText);
+
+ let suffix = this.text.substring(this.endOffset).trim();
+ if (suffix) {
+ suffix = ' ' + suffix;
+ newBraille.push(suffix);
+ }
+
+ this.startOffset = braille.startOffset = prefix.length;
+ this.text = braille.text = newBraille.join('') + ' ';
+ this.endOffset = braille.endOffset = braille.text.length - suffix.length;
+ braille.selectionStart = this.selectionStart;
+ braille.selectionEnd = this.selectionEnd;
+
+ return braille;
+ },
+
+ adjustSelection: function adjustSelection(aSelection) {
+ let braille = {};
+
+ braille.startOffset = this.startOffset;
+ braille.endOffset = this.endOffset;
+ braille.text = this.text;
+ this.selectionStart = braille.selectionStart =
+ aSelection.selectionStart + this.startOffset;
+ this.selectionEnd = braille.selectionEnd =
+ aSelection.selectionEnd + this.startOffset;
+
+ return braille;
+ }
+ },
+
+ start: function start() {
+ Cu.import('resource://gre/modules/Geometry.jsm');
+ },
+
+ stop: function stop() {
+ if (this.highlightBox) {
+ let highlightBox = this.highlightBox.get();
+ if (highlightBox) {
+ highlightBox.remove();
+ }
+ delete this.highlightBox;
+ }
+ },
+
+ B2G: function B2G(aDetails) {
+ Utils.dispatchChromeEvent('accessibility-output', aDetails);
+ },
+
+ Visual: function Visual(aDetail, aBrowser) {
+ switch (aDetail.eventType) {
+ case 'viewport-change':
+ case 'vc-change':
+ {
+ let highlightBox = null;
+ if (!this.highlightBox) {
+ let doc = Utils.win.document;
+ // Add highlight box
+ highlightBox = Utils.win.document.
+ createElementNS('http://www.w3.org/1999/xhtml', 'div');
+ let parent = doc.body || doc.documentElement;
+ parent.appendChild(highlightBox);
+ highlightBox.id = 'virtual-cursor-box';
+
+ // Add highlight inset for inner shadow
+ highlightBox.appendChild(
+ doc.createElementNS('http://www.w3.org/1999/xhtml', 'div'));
+
+ this.highlightBox = Cu.getWeakReference(highlightBox);
+ } else {
+ highlightBox = this.highlightBox.get();
+ }
+
+ let padding = aDetail.padding;
+ let r = AccessFu.adjustContentBounds(aDetail.bounds, aBrowser, true);
+
+ // First hide it to avoid flickering when changing the style.
+ highlightBox.classList.remove('show');
+ highlightBox.style.top = (r.top - padding) + 'px';
+ highlightBox.style.left = (r.left - padding) + 'px';
+ highlightBox.style.width = (r.width + padding*2) + 'px';
+ highlightBox.style.height = (r.height + padding*2) + 'px';
+ highlightBox.classList.add('show');
+
+ break;
+ }
+ case 'tabstate-change':
+ {
+ let highlightBox = this.highlightBox ? this.highlightBox.get() : null;
+ if (highlightBox) {
+ highlightBox.classList.remove('show');
+ }
+ break;
+ }
+ }
+ },
+
+ get androidBridge() {
+ delete this.androidBridge;
+ if (Utils.MozBuildApp === 'mobile/android') {
+ this.androidBridge = Services.androidBridge;
+ } else {
+ this.androidBridge = null;
+ }
+ return this.androidBridge;
+ },
+
+ Android: function Android(aDetails, aBrowser) {
+ const ANDROID_VIEW_TEXT_CHANGED = 0x10;
+ const ANDROID_VIEW_TEXT_SELECTION_CHANGED = 0x2000;
+
+ if (!this.androidBridge) {
+ return;
+ }
+
+ for (let androidEvent of aDetails) {
+ androidEvent.type = 'Accessibility:Event';
+ if (androidEvent.bounds) {
+ androidEvent.bounds = AccessFu.adjustContentBounds(
+ androidEvent.bounds, aBrowser);
+ }
+
+ switch(androidEvent.eventType) {
+ case ANDROID_VIEW_TEXT_CHANGED:
+ androidEvent.brailleOutput = this.brailleState.adjustText(
+ androidEvent.text);
+ break;
+ case ANDROID_VIEW_TEXT_SELECTION_CHANGED:
+ androidEvent.brailleOutput = this.brailleState.adjustSelection(
+ androidEvent.brailleOutput);
+ break;
+ default:
+ androidEvent.brailleOutput = this.brailleState.init(
+ androidEvent.brailleOutput);
+ break;
+ }
+ this.androidBridge.handleGeckoMessage(androidEvent);
+ }
+ },
+
+ Braille: function Braille(aDetails) {
+ Logger.debug('Braille output: ' + aDetails.output);
+ }
+};
+
+var Input = {
+ editState: {},
+
+ start: function start() {
+ // XXX: This is too disruptive on desktop for now.
+ // Might need to add special modifiers.
+ if (Utils.MozBuildApp != 'browser') {
+ Utils.win.document.addEventListener('keypress', this, true);
+ }
+ Utils.win.addEventListener('mozAccessFuGesture', this, true);
+ },
+
+ stop: function stop() {
+ if (Utils.MozBuildApp != 'browser') {
+ Utils.win.document.removeEventListener('keypress', this, true);
+ }
+ Utils.win.removeEventListener('mozAccessFuGesture', this, true);
+ },
+
+ handleEvent: function Input_handleEvent(aEvent) {
+ try {
+ switch (aEvent.type) {
+ case 'keypress':
+ this._handleKeypress(aEvent);
+ break;
+ case 'mozAccessFuGesture':
+ this._handleGesture(aEvent.detail);
+ break;
+ }
+ } catch (x) {
+ Logger.logException(x);
+ }
+ },
+
+ _handleGesture: function _handleGesture(aGesture) {
+ let gestureName = aGesture.type + aGesture.touches.length;
+ Logger.debug('Gesture', aGesture.type,
+ '(fingers: ' + aGesture.touches.length + ')');
+
+ switch (gestureName) {
+ case 'dwell1':
+ case 'explore1':
+ this.moveToPoint('Simple', aGesture.touches[0].x,
+ aGesture.touches[0].y);
+ break;
+ case 'doubletap1':
+ this.activateCurrent();
+ break;
+ case 'doubletaphold1':
+ Utils.dispatchChromeEvent('accessibility-control', 'quicknav-menu');
+ break;
+ case 'swiperight1':
+ this.moveCursor('moveNext', 'Simple', 'gestures');
+ break;
+ case 'swipeleft1':
+ this.moveCursor('movePrevious', 'Simple', 'gesture');
+ break;
+ case 'swipeup1':
+ this.moveCursor(
+ 'movePrevious', this.quickNavMode.current, 'gesture', true);
+ break;
+ case 'swipedown1':
+ this.moveCursor('moveNext', this.quickNavMode.current, 'gesture', true);
+ break;
+ case 'exploreend1':
+ case 'dwellend1':
+ this.activateCurrent(null, true);
+ break;
+ case 'swiperight2':
+ if (aGesture.edge) {
+ Utils.dispatchChromeEvent('accessibility-control',
+ 'edge-swipe-right');
+ break;
+ }
+ this.sendScrollMessage(-1, true);
+ break;
+ case 'swipedown2':
+ if (aGesture.edge) {
+ Utils.dispatchChromeEvent('accessibility-control', 'edge-swipe-down');
+ break;
+ }
+ this.sendScrollMessage(-1);
+ break;
+ case 'swipeleft2':
+ if (aGesture.edge) {
+ Utils.dispatchChromeEvent('accessibility-control', 'edge-swipe-left');
+ break;
+ }
+ this.sendScrollMessage(1, true);
+ break;
+ case 'swipeup2':
+ if (aGesture.edge) {
+ Utils.dispatchChromeEvent('accessibility-control', 'edge-swipe-up');
+ break;
+ }
+ this.sendScrollMessage(1);
+ break;
+ case 'explore2':
+ Utils.CurrentBrowser.contentWindow.scrollBy(
+ -aGesture.deltaX, -aGesture.deltaY);
+ break;
+ case 'swiperight3':
+ this.moveCursor('moveNext', this.quickNavMode.current, 'gesture');
+ break;
+ case 'swipeleft3':
+ this.moveCursor('movePrevious', this.quickNavMode.current, 'gesture');
+ break;
+ case 'swipedown3':
+ this.quickNavMode.next();
+ AccessFu.announce('quicknav_' + this.quickNavMode.current);
+ break;
+ case 'swipeup3':
+ this.quickNavMode.previous();
+ AccessFu.announce('quicknav_' + this.quickNavMode.current);
+ break;
+ case 'tripletap3':
+ Utils.dispatchChromeEvent('accessibility-control', 'toggle-shade');
+ break;
+ case 'tap2':
+ Utils.dispatchChromeEvent('accessibility-control', 'toggle-pause');
+ break;
+ }
+ },
+
+ _handleKeypress: function _handleKeypress(aEvent) {
+ let target = aEvent.target;
+
+ // Ignore keys with modifiers so the content could take advantage of them.
+ if (aEvent.ctrlKey || aEvent.altKey || aEvent.metaKey) {
+ return;
+ }
+
+ switch (aEvent.keyCode) {
+ case 0:
+ // an alphanumeric key was pressed, handle it separately.
+ // If it was pressed with either alt or ctrl, just pass through.
+ // If it was pressed with meta, pass the key on without the meta.
+ if (this.editState.editing) {
+ return;
+ }
+
+ let key = String.fromCharCode(aEvent.charCode);
+ try {
+ let [methodName, rule] = this.keyMap[key];
+ this.moveCursor(methodName, rule, 'keyboard');
+ } catch (x) {
+ return;
+ }
+ break;
+ case aEvent.DOM_VK_RIGHT:
+ if (this.editState.editing) {
+ if (!this.editState.atEnd) {
+ // Don't move forward if caret is not at end of entry.
+ // XXX: Fix for rtl
+ return;
+ } else {
+ target.blur();
+ }
+ }
+ this.moveCursor(aEvent.shiftKey ?
+ 'moveLast' : 'moveNext', 'Simple', 'keyboard');
+ break;
+ case aEvent.DOM_VK_LEFT:
+ if (this.editState.editing) {
+ if (!this.editState.atStart) {
+ // Don't move backward if caret is not at start of entry.
+ // XXX: Fix for rtl
+ return;
+ } else {
+ target.blur();
+ }
+ }
+ this.moveCursor(aEvent.shiftKey ?
+ 'moveFirst' : 'movePrevious', 'Simple', 'keyboard');
+ break;
+ case aEvent.DOM_VK_UP:
+ if (this.editState.multiline) {
+ if (!this.editState.atStart) {
+ // Don't blur content if caret is not at start of text area.
+ return;
+ } else {
+ target.blur();
+ }
+ }
+
+ if (Utils.MozBuildApp == 'mobile/android') {
+ // Return focus to native Android browser chrome.
+ Services.androidBridge.handleGeckoMessage(
+ { type: 'ToggleChrome:Focus' });
+ }
+ break;
+ case aEvent.DOM_VK_RETURN:
+ if (this.editState.editing) {
+ return;
+ }
+ this.activateCurrent();
+ break;
+ default:
+ return;
+ }
+
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+ },
+
+ moveToPoint: function moveToPoint(aRule, aX, aY) {
+ // XXX: Bug 1013408 - There is no alignment between the chrome window's
+ // viewport size and the content viewport size in Android. This makes
+ // sending mouse events beyond its bounds impossible.
+ if (Utils.MozBuildApp === 'mobile/android') {
+ let mm = Utils.getMessageManager(Utils.CurrentBrowser);
+ mm.sendAsyncMessage('AccessFu:MoveToPoint',
+ {rule: aRule, x: aX, y: aY, origin: 'top'});
+ } else {
+ let win = Utils.win;
+ Utils.winUtils.sendMouseEvent('mousemove',
+ aX - win.mozInnerScreenX, aY - win.mozInnerScreenY, 0, 0, 0);
+ }
+ },
+
+ moveCursor: function moveCursor(aAction, aRule, aInputType, aAdjustRange) {
+ let mm = Utils.getMessageManager(Utils.CurrentBrowser);
+ mm.sendAsyncMessage('AccessFu:MoveCursor',
+ { action: aAction, rule: aRule,
+ origin: 'top', inputType: aInputType,
+ adjustRange: aAdjustRange });
+ },
+
+ androidScroll: function androidScroll(aDirection) {
+ let mm = Utils.getMessageManager(Utils.CurrentBrowser);
+ mm.sendAsyncMessage('AccessFu:AndroidScroll',
+ { direction: aDirection, origin: 'top' });
+ },
+
+ moveByGranularity: function moveByGranularity(aDetails) {
+ const GRANULARITY_PARAGRAPH = 8;
+ const GRANULARITY_LINE = 4;
+
+ if (!this.editState.editing) {
+ if (aDetails.granularity & (GRANULARITY_PARAGRAPH | GRANULARITY_LINE)) {
+ this.moveCursor('move' + aDetails.direction, 'Simple', 'gesture');
+ return;
+ }
+ } else {
+ aDetails.atStart = this.editState.atStart;
+ aDetails.atEnd = this.editState.atEnd;
+ }
+
+ let mm = Utils.getMessageManager(Utils.CurrentBrowser);
+ let type = this.editState.editing ? 'AccessFu:MoveCaret' :
+ 'AccessFu:MoveByGranularity';
+ mm.sendAsyncMessage(type, aDetails);
+ },
+
+ activateCurrent: function activateCurrent(aData, aActivateIfKey = false) {
+ let mm = Utils.getMessageManager(Utils.CurrentBrowser);
+ let offset = aData && typeof aData.keyIndex === 'number' ?
+ aData.keyIndex - Output.brailleState.startOffset : -1;
+
+ mm.sendAsyncMessage('AccessFu:Activate',
+ {offset: offset, activateIfKey: aActivateIfKey});
+ },
+
+ sendContextMenuMessage: function sendContextMenuMessage() {
+ let mm = Utils.getMessageManager(Utils.CurrentBrowser);
+ mm.sendAsyncMessage('AccessFu:ContextMenu', {});
+ },
+
+ setEditState: function setEditState(aEditState) {
+ Logger.debug(() => { return ['setEditState', JSON.stringify(aEditState)] });
+ this.editState = aEditState;
+ },
+
+ // XXX: This is here for backwards compatability with screen reader simulator
+ // it should be removed when the extension is updated on amo.
+ scroll: function scroll(aPage, aHorizontal) {
+ this.sendScrollMessage(aPage, aHorizontal);
+ },
+
+ sendScrollMessage: function sendScrollMessage(aPage, aHorizontal) {
+ let mm = Utils.getMessageManager(Utils.CurrentBrowser);
+ mm.sendAsyncMessage('AccessFu:Scroll',
+ {page: aPage, horizontal: aHorizontal, origin: 'top'});
+ },
+
+ doScroll: function doScroll(aDetails) {
+ let horizontal = aDetails.horizontal;
+ let page = aDetails.page;
+ let p = AccessFu.adjustContentBounds(
+ aDetails.bounds, Utils.CurrentBrowser, true).center();
+ Utils.winUtils.sendWheelEvent(p.x, p.y,
+ horizontal ? page : 0, horizontal ? 0 : page, 0,
+ Utils.win.WheelEvent.DOM_DELTA_PAGE, 0, 0, 0, 0);
+ },
+
+ get keyMap() {
+ delete this.keyMap;
+ this.keyMap = {
+ a: ['moveNext', 'Anchor'],
+ A: ['movePrevious', 'Anchor'],
+ b: ['moveNext', 'Button'],
+ B: ['movePrevious', 'Button'],
+ c: ['moveNext', 'Combobox'],
+ C: ['movePrevious', 'Combobox'],
+ d: ['moveNext', 'Landmark'],
+ D: ['movePrevious', 'Landmark'],
+ e: ['moveNext', 'Entry'],
+ E: ['movePrevious', 'Entry'],
+ f: ['moveNext', 'FormElement'],
+ F: ['movePrevious', 'FormElement'],
+ g: ['moveNext', 'Graphic'],
+ G: ['movePrevious', 'Graphic'],
+ h: ['moveNext', 'Heading'],
+ H: ['movePrevious', 'Heading'],
+ i: ['moveNext', 'ListItem'],
+ I: ['movePrevious', 'ListItem'],
+ k: ['moveNext', 'Link'],
+ K: ['movePrevious', 'Link'],
+ l: ['moveNext', 'List'],
+ L: ['movePrevious', 'List'],
+ p: ['moveNext', 'PageTab'],
+ P: ['movePrevious', 'PageTab'],
+ r: ['moveNext', 'RadioButton'],
+ R: ['movePrevious', 'RadioButton'],
+ s: ['moveNext', 'Separator'],
+ S: ['movePrevious', 'Separator'],
+ t: ['moveNext', 'Table'],
+ T: ['movePrevious', 'Table'],
+ x: ['moveNext', 'Checkbox'],
+ X: ['movePrevious', 'Checkbox']
+ };
+
+ return this.keyMap;
+ },
+
+ quickNavMode: {
+ get current() {
+ return this.modes[this._currentIndex];
+ },
+
+ previous: function quickNavMode_previous() {
+ Services.prefs.setIntPref(QUICKNAV_INDEX_PREF,
+ this._currentIndex > 0 ?
+ this._currentIndex - 1 : this.modes.length - 1);
+ },
+
+ next: function quickNavMode_next() {
+ Services.prefs.setIntPref(QUICKNAV_INDEX_PREF,
+ this._currentIndex + 1 >= this.modes.length ?
+ 0 : this._currentIndex + 1);
+ },
+
+ updateModes: function updateModes(aModes) {
+ if (aModes) {
+ this.modes = aModes.split(',');
+ } else {
+ this.modes = [];
+ }
+ },
+
+ updateCurrentMode: function updateCurrentMode(aModeIndex) {
+ Logger.debug('Quicknav mode:', this.modes[aModeIndex]);
+ this._currentIndex = aModeIndex;
+ }
+ }
+};
+AccessFu.Input = Input;
diff --git a/accessible/jsat/Constants.jsm b/accessible/jsat/Constants.jsm
new file mode 100644
index 000000000..152604431
--- /dev/null
+++ b/accessible/jsat/Constants.jsm
@@ -0,0 +1,59 @@
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+
+this.EXPORTED_SYMBOLS = ['Roles', 'Events', 'Relations',
+ 'Filters', 'States', 'Prefilters'];
+
+function ConstantsMap (aObject, aPrefix, aMap = {}, aModifier = null) {
+ let offset = aPrefix.length;
+ for (var name in aObject) {
+ if (name.indexOf(aPrefix) === 0) {
+ aMap[name.slice(offset)] = aModifier ?
+ aModifier(aObject[name]) : aObject[name];
+ }
+ }
+
+ return aMap;
+}
+
+XPCOMUtils.defineLazyGetter(
+ this, 'Roles',
+ function() {
+ return ConstantsMap(Ci.nsIAccessibleRole, 'ROLE_');
+ });
+
+XPCOMUtils.defineLazyGetter(
+ this, 'Events',
+ function() {
+ return ConstantsMap(Ci.nsIAccessibleEvent, 'EVENT_');
+ });
+
+XPCOMUtils.defineLazyGetter(
+ this, 'Relations',
+ function() {
+ return ConstantsMap(Ci.nsIAccessibleRelation, 'RELATION_');
+ });
+
+XPCOMUtils.defineLazyGetter(
+ this, 'Prefilters',
+ function() {
+ return ConstantsMap(Ci.nsIAccessibleTraversalRule, 'PREFILTER_');
+ });
+
+XPCOMUtils.defineLazyGetter(
+ this, 'Filters',
+ function() {
+ return ConstantsMap(Ci.nsIAccessibleTraversalRule, 'FILTER_');
+ });
+
+XPCOMUtils.defineLazyGetter(
+ this, 'States',
+ function() {
+ let statesMap = ConstantsMap(Ci.nsIAccessibleStates, 'STATE_', {},
+ (val) => { return { base: val, extended: 0 }; });
+ ConstantsMap(Ci.nsIAccessibleStates, 'EXT_STATE_', statesMap,
+ (val) => { return { base: 0, extended: val }; });
+ return statesMap;
+ });
diff --git a/accessible/jsat/ContentControl.jsm b/accessible/jsat/ContentControl.jsm
new file mode 100644
index 000000000..f5fd471ba
--- /dev/null
+++ b/accessible/jsat/ContentControl.jsm
@@ -0,0 +1,528 @@
+/* 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/. */
+
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, 'Services',
+ 'resource://gre/modules/Services.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
+ 'resource://gre/modules/accessibility/Utils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Logger',
+ 'resource://gre/modules/accessibility/Utils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Roles',
+ 'resource://gre/modules/accessibility/Constants.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'TraversalRules',
+ 'resource://gre/modules/accessibility/Traversal.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'TraversalHelper',
+ 'resource://gre/modules/accessibility/Traversal.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Presentation',
+ 'resource://gre/modules/accessibility/Presentation.jsm');
+
+this.EXPORTED_SYMBOLS = ['ContentControl'];
+
+const MOVEMENT_GRANULARITY_CHARACTER = 1;
+const MOVEMENT_GRANULARITY_WORD = 2;
+const MOVEMENT_GRANULARITY_PARAGRAPH = 8;
+
+this.ContentControl = function ContentControl(aContentScope) {
+ this._contentScope = Cu.getWeakReference(aContentScope);
+ this._childMessageSenders = new WeakMap();
+};
+
+this.ContentControl.prototype = {
+ messagesOfInterest: ['AccessFu:MoveCursor',
+ 'AccessFu:ClearCursor',
+ 'AccessFu:MoveToPoint',
+ 'AccessFu:AutoMove',
+ 'AccessFu:Activate',
+ 'AccessFu:MoveCaret',
+ 'AccessFu:MoveByGranularity',
+ 'AccessFu:AndroidScroll'],
+
+ start: function cc_start() {
+ let cs = this._contentScope.get();
+ for (let message of this.messagesOfInterest) {
+ cs.addMessageListener(message, this);
+ }
+ cs.addEventListener('mousemove', this);
+ },
+
+ stop: function cc_stop() {
+ let cs = this._contentScope.get();
+ for (let message of this.messagesOfInterest) {
+ cs.removeMessageListener(message, this);
+ }
+ cs.removeEventListener('mousemove', this);
+ },
+
+ get document() {
+ return this._contentScope.get().content.document;
+ },
+
+ get window() {
+ return this._contentScope.get().content;
+ },
+
+ get vc() {
+ return Utils.getVirtualCursor(this.document);
+ },
+
+ receiveMessage: function cc_receiveMessage(aMessage) {
+ Logger.debug(() => {
+ return ['ContentControl.receiveMessage',
+ aMessage.name,
+ JSON.stringify(aMessage.json)];
+ });
+
+ // If we get an explicit message, we should immediately cancel any autoMove
+ this.cancelAutoMove();
+
+ try {
+ let func = this['handle' + aMessage.name.slice(9)]; // 'AccessFu:'.length
+ if (func) {
+ func.bind(this)(aMessage);
+ } else {
+ Logger.warning('ContentControl: Unhandled message:', aMessage.name);
+ }
+ } catch (x) {
+ Logger.logException(
+ x, 'Error handling message: ' + JSON.stringify(aMessage.json));
+ }
+ },
+
+ handleAndroidScroll: function cc_handleAndroidScroll(aMessage) {
+ let vc = this.vc;
+ let position = vc.position;
+
+ if (aMessage.json.origin != 'child' && this.sendToChild(vc, aMessage)) {
+ // Forwarded succesfully to child cursor.
+ return;
+ }
+
+ // Counter-intuitive, but scrolling backward (ie. up), actually should
+ // increase range values.
+ if (this.adjustRange(position, aMessage.json.direction === 'backward')) {
+ return;
+ }
+
+ this._contentScope.get().sendAsyncMessage('AccessFu:DoScroll',
+ { bounds: Utils.getBounds(position, true),
+ page: aMessage.json.direction === 'forward' ? 1 : -1,
+ horizontal: false });
+ },
+
+ handleMoveCursor: function cc_handleMoveCursor(aMessage) {
+ let origin = aMessage.json.origin;
+ let action = aMessage.json.action;
+ let adjustRange = aMessage.json.adjustRange;
+ let vc = this.vc;
+
+ if (origin != 'child' && this.sendToChild(vc, aMessage)) {
+ // Forwarded succesfully to child cursor.
+ return;
+ }
+
+ if (adjustRange && this.adjustRange(vc.position, action === 'moveNext')) {
+ return;
+ }
+
+ let moved = TraversalHelper.move(vc, action, aMessage.json.rule);
+
+ if (moved) {
+ if (origin === 'child') {
+ // We just stepped out of a child, clear child cursor.
+ Utils.getMessageManager(aMessage.target).sendAsyncMessage(
+ 'AccessFu:ClearCursor', {});
+ } else {
+ // We potentially landed on a new child cursor. If so, we want to
+ // either be on the first or last item in the child doc.
+ let childAction = action;
+ if (action === 'moveNext') {
+ childAction = 'moveFirst';
+ } else if (action === 'movePrevious') {
+ childAction = 'moveLast';
+ }
+
+ // Attempt to forward move to a potential child cursor in our
+ // new position.
+ this.sendToChild(vc, aMessage, { action: childAction }, true);
+ }
+ } else if (!this._childMessageSenders.has(aMessage.target) &&
+ origin !== 'top') {
+ // We failed to move, and the message is not from a parent, so forward
+ // to it.
+ this.sendToParent(aMessage);
+ } else {
+ this._contentScope.get().sendAsyncMessage('AccessFu:Present',
+ Presentation.noMove(action));
+ }
+ },
+
+ handleEvent: function cc_handleEvent(aEvent) {
+ if (aEvent.type === 'mousemove') {
+ this.handleMoveToPoint(
+ { json: { x: aEvent.screenX, y: aEvent.screenY, rule: 'Simple' } });
+ }
+ if (!Utils.getMessageManager(aEvent.target)) {
+ aEvent.preventDefault();
+ } else {
+ aEvent.target.focus();
+ }
+ },
+
+ handleMoveToPoint: function cc_handleMoveToPoint(aMessage) {
+ let [x, y] = [aMessage.json.x, aMessage.json.y];
+ let rule = TraversalRules[aMessage.json.rule];
+
+ let dpr = this.window.devicePixelRatio;
+ this.vc.moveToPoint(rule, x * dpr, y * dpr, true);
+ },
+
+ handleClearCursor: function cc_handleClearCursor(aMessage) {
+ let forwarded = this.sendToChild(this.vc, aMessage);
+ this.vc.position = null;
+ if (!forwarded) {
+ this._contentScope.get().sendAsyncMessage('AccessFu:CursorCleared');
+ }
+ this.document.activeElement.blur();
+ },
+
+ handleAutoMove: function cc_handleAutoMove(aMessage) {
+ this.autoMove(null, aMessage.json);
+ },
+
+ handleActivate: function cc_handleActivate(aMessage) {
+ let activateAccessible = (aAccessible) => {
+ Logger.debug(() => {
+ return ['activateAccessible', Logger.accessibleToString(aAccessible)];
+ });
+ try {
+ if (aMessage.json.activateIfKey &&
+ !Utils.isActivatableOnFingerUp(aAccessible)) {
+ // Only activate keys, don't do anything on other objects.
+ return;
+ }
+ } catch (e) {
+ // accessible is invalid. Silently fail.
+ return;
+ }
+
+ if (aAccessible.actionCount > 0) {
+ aAccessible.doAction(0);
+ } else {
+ let control = Utils.getEmbeddedControl(aAccessible);
+ if (control && control.actionCount > 0) {
+ control.doAction(0);
+ }
+
+ // XXX Some mobile widget sets do not expose actions properly
+ // (via ARIA roles, etc.), so we need to generate a click.
+ // Could possibly be made simpler in the future. Maybe core
+ // engine could expose nsCoreUtiles::DispatchMouseEvent()?
+ let docAcc = Utils.AccService.getAccessibleFor(this.document);
+ let docX = {}, docY = {}, docW = {}, docH = {};
+ docAcc.getBounds(docX, docY, docW, docH);
+
+ let objX = {}, objY = {}, objW = {}, objH = {};
+ aAccessible.getBounds(objX, objY, objW, objH);
+
+ let x = Math.round((objX.value - docX.value) + objW.value / 2);
+ let y = Math.round((objY.value - docY.value) + objH.value / 2);
+
+ let node = aAccessible.DOMNode || aAccessible.parent.DOMNode;
+
+ for (let eventType of ['mousedown', 'mouseup']) {
+ let evt = this.document.createEvent('MouseEvents');
+ evt.initMouseEvent(eventType, true, true, this.window,
+ x, y, 0, 0, 0, false, false, false, false, 0, null);
+ node.dispatchEvent(evt);
+ }
+ }
+
+ if (!Utils.isActivatableOnFingerUp(aAccessible)) {
+ // Keys will typically have a sound of their own.
+ this._contentScope.get().sendAsyncMessage('AccessFu:Present',
+ Presentation.actionInvoked(aAccessible, 'click'));
+ }
+ };
+
+ let focusedAcc = Utils.AccService.getAccessibleFor(
+ this.document.activeElement);
+ if (focusedAcc && this.vc.position === focusedAcc
+ && focusedAcc.role === Roles.ENTRY) {
+ let accText = focusedAcc.QueryInterface(Ci.nsIAccessibleText);
+ let oldOffset = accText.caretOffset;
+ let newOffset = aMessage.json.offset;
+ let text = accText.getText(0, accText.characterCount);
+
+ if (newOffset >= 0 && newOffset <= accText.characterCount) {
+ accText.caretOffset = newOffset;
+ }
+
+ this.presentCaretChange(text, oldOffset, accText.caretOffset);
+ return;
+ }
+
+ // recursively find a descendant that is activatable.
+ let getActivatableDescendant = (aAccessible) => {
+ if (aAccessible.actionCount > 0) {
+ return aAccessible;
+ }
+
+ for (let acc = aAccessible.firstChild; acc; acc = acc.nextSibling) {
+ let activatable = getActivatableDescendant(acc);
+ if (activatable) {
+ return activatable;
+ }
+ }
+
+ return null;
+ };
+
+ let vc = this.vc;
+ if (!this.sendToChild(vc, aMessage, null, true)) {
+ let position = vc.position;
+ activateAccessible(getActivatableDescendant(position) || position);
+ }
+ },
+
+ adjustRange: function cc_adjustRange(aAccessible, aStepUp) {
+ let acc = Utils.getEmbeddedControl(aAccessible) || aAccessible;
+ try {
+ acc.QueryInterface(Ci.nsIAccessibleValue);
+ } catch (x) {
+ // This is not an adjustable, return false.
+ return false;
+ }
+
+ let elem = acc.DOMNode;
+ if (!elem) {
+ return false;
+ }
+
+ if (elem.tagName === 'INPUT' && elem.type === 'range') {
+ elem[aStepUp ? 'stepDown' : 'stepUp']();
+ let evt = this.document.createEvent('UIEvent');
+ evt.initEvent('change', true, true);
+ elem.dispatchEvent(evt);
+ } else {
+ let evt = this.document.createEvent('KeyboardEvent');
+ let keycode = aStepUp ? evt.DOM_VK_DOWN : evt.DOM_VK_UP;
+ evt.initKeyEvent(
+ "keypress", false, true, null, false, false, false, false, keycode, 0);
+ elem.dispatchEvent(evt);
+ }
+
+ return true;
+ },
+
+ handleMoveByGranularity: function cc_handleMoveByGranularity(aMessage) {
+ // XXX: Add sendToChild. Right now this is only used in Android, so no need.
+ let direction = aMessage.json.direction;
+ let granularity;
+
+ switch(aMessage.json.granularity) {
+ case MOVEMENT_GRANULARITY_CHARACTER:
+ granularity = Ci.nsIAccessiblePivot.CHAR_BOUNDARY;
+ break;
+ case MOVEMENT_GRANULARITY_WORD:
+ granularity = Ci.nsIAccessiblePivot.WORD_BOUNDARY;
+ break;
+ default:
+ return;
+ }
+
+ if (direction === 'Previous') {
+ this.vc.movePreviousByText(granularity);
+ } else if (direction === 'Next') {
+ this.vc.moveNextByText(granularity);
+ }
+ },
+
+ presentCaretChange: function cc_presentCaretChange(
+ aText, aOldOffset, aNewOffset) {
+ if (aOldOffset !== aNewOffset) {
+ let msg = Presentation.textSelectionChanged(aText, aNewOffset, aNewOffset,
+ aOldOffset, aOldOffset, true);
+ this._contentScope.get().sendAsyncMessage('AccessFu:Present', msg);
+ }
+ },
+
+ handleMoveCaret: function cc_handleMoveCaret(aMessage) {
+ let direction = aMessage.json.direction;
+ let granularity = aMessage.json.granularity;
+ let accessible = this.vc.position;
+ let accText = accessible.QueryInterface(Ci.nsIAccessibleText);
+ let oldOffset = accText.caretOffset;
+ let text = accText.getText(0, accText.characterCount);
+
+ let start = {}, end = {};
+ if (direction === 'Previous' && !aMessage.json.atStart) {
+ switch (granularity) {
+ case MOVEMENT_GRANULARITY_CHARACTER:
+ accText.caretOffset--;
+ break;
+ case MOVEMENT_GRANULARITY_WORD:
+ accText.getTextBeforeOffset(accText.caretOffset,
+ Ci.nsIAccessibleText.BOUNDARY_WORD_START, start, end);
+ accText.caretOffset = end.value === accText.caretOffset ?
+ start.value : end.value;
+ break;
+ case MOVEMENT_GRANULARITY_PARAGRAPH:
+ let startOfParagraph = text.lastIndexOf('\n', accText.caretOffset - 1);
+ accText.caretOffset = startOfParagraph !== -1 ? startOfParagraph : 0;
+ break;
+ }
+ } else if (direction === 'Next' && !aMessage.json.atEnd) {
+ switch (granularity) {
+ case MOVEMENT_GRANULARITY_CHARACTER:
+ accText.caretOffset++;
+ break;
+ case MOVEMENT_GRANULARITY_WORD:
+ accText.getTextAtOffset(accText.caretOffset,
+ Ci.nsIAccessibleText.BOUNDARY_WORD_END, start, end);
+ accText.caretOffset = end.value;
+ break;
+ case MOVEMENT_GRANULARITY_PARAGRAPH:
+ accText.caretOffset = text.indexOf('\n', accText.caretOffset + 1);
+ break;
+ }
+ }
+
+ this.presentCaretChange(text, oldOffset, accText.caretOffset);
+ },
+
+ getChildCursor: function cc_getChildCursor(aAccessible) {
+ let acc = aAccessible || this.vc.position;
+ if (Utils.isAliveAndVisible(acc) && acc.role === Roles.INTERNAL_FRAME) {
+ let domNode = acc.DOMNode;
+ let mm = this._childMessageSenders.get(domNode, null);
+ if (!mm) {
+ mm = Utils.getMessageManager(domNode);
+ mm.addWeakMessageListener('AccessFu:MoveCursor', this);
+ this._childMessageSenders.set(domNode, mm);
+ }
+
+ return mm;
+ }
+
+ return null;
+ },
+
+ sendToChild: function cc_sendToChild(aVirtualCursor, aMessage, aReplacer,
+ aFocus) {
+ let position = aVirtualCursor.position;
+ let mm = this.getChildCursor(position);
+ if (!mm) {
+ return false;
+ }
+
+ if (aFocus) {
+ position.takeFocus();
+ }
+
+ // XXX: This is a silly way to make a deep copy
+ let newJSON = JSON.parse(JSON.stringify(aMessage.json));
+ newJSON.origin = 'parent';
+ for (let attr in aReplacer) {
+ newJSON[attr] = aReplacer[attr];
+ }
+
+ mm.sendAsyncMessage(aMessage.name, newJSON);
+ return true;
+ },
+
+ sendToParent: function cc_sendToParent(aMessage) {
+ // XXX: This is a silly way to make a deep copy
+ let newJSON = JSON.parse(JSON.stringify(aMessage.json));
+ newJSON.origin = 'child';
+ aMessage.target.sendAsyncMessage(aMessage.name, newJSON);
+ },
+
+ /**
+ * Move cursor and/or present its location.
+ * aOptions could have any of these fields:
+ * - delay: in ms, before actual move is performed. Another autoMove call
+ * would cancel it. Useful if we want to wait for a possible trailing
+ * focus move. Default 0.
+ * - noOpIfOnScreen: if accessible is alive and visible, don't do anything.
+ * - forcePresent: present cursor location, whether we move or don't.
+ * - moveToFocused: if there is a focused accessible move to that. This takes
+ * precedence over given anchor.
+ * - moveMethod: pivot move method to use, default is 'moveNext',
+ */
+ autoMove: function cc_autoMove(aAnchor, aOptions = {}) {
+ this.cancelAutoMove();
+
+ let moveFunc = () => {
+ let vc = this.vc;
+ let acc = aAnchor;
+ let rule = aOptions.onScreenOnly ?
+ TraversalRules.SimpleOnScreen : TraversalRules.Simple;
+ let forcePresentFunc = () => {
+ if (aOptions.forcePresent) {
+ this._contentScope.get().sendAsyncMessage(
+ 'AccessFu:Present', Presentation.pivotChanged(
+ vc.position, null, Ci.nsIAccessiblePivot.REASON_NONE,
+ vc.startOffset, vc.endOffset, false));
+ }
+ };
+
+ if (aOptions.noOpIfOnScreen &&
+ Utils.isAliveAndVisible(vc.position, true)) {
+ forcePresentFunc();
+ return;
+ }
+
+ if (aOptions.moveToFocused) {
+ acc = Utils.AccService.getAccessibleFor(
+ this.document.activeElement) || acc;
+ }
+
+ let moved = false;
+ let moveMethod = aOptions.moveMethod || 'moveNext'; // default is moveNext
+ let moveFirstOrLast = moveMethod in ['moveFirst', 'moveLast'];
+ if (!moveFirstOrLast || acc) {
+ // We either need next/previous or there is an anchor we need to use.
+ moved = vc[moveFirstOrLast ? 'moveNext' : moveMethod](rule, acc, true,
+ false);
+ }
+ if (moveFirstOrLast && !moved) {
+ // We move to first/last after no anchor move happened or succeeded.
+ moved = vc[moveMethod](rule, false);
+ }
+
+ let sentToChild = this.sendToChild(vc, {
+ name: 'AccessFu:AutoMove',
+ json: {
+ moveMethod: aOptions.moveMethod,
+ moveToFocused: aOptions.moveToFocused,
+ noOpIfOnScreen: true,
+ forcePresent: true
+ }
+ }, null, true);
+
+ if (!moved && !sentToChild) {
+ forcePresentFunc();
+ }
+ };
+
+ if (aOptions.delay) {
+ this._autoMove = this.window.setTimeout(moveFunc, aOptions.delay);
+ } else {
+ moveFunc();
+ }
+ },
+
+ cancelAutoMove: function cc_cancelAutoMove() {
+ this.window.clearTimeout(this._autoMove);
+ this._autoMove = 0;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
+ Ci.nsIMessageListener
+ ])
+};
diff --git a/accessible/jsat/EventManager.jsm b/accessible/jsat/EventManager.jsm
new file mode 100644
index 000000000..4d635eb68
--- /dev/null
+++ b/accessible/jsat/EventManager.jsm
@@ -0,0 +1,723 @@
+/* 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/. */
+
+'use strict';
+
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+const TEXT_NODE = 3;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Services',
+ 'resource://gre/modules/Services.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
+ 'resource://gre/modules/accessibility/Utils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Logger',
+ 'resource://gre/modules/accessibility/Utils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Presentation',
+ 'resource://gre/modules/accessibility/Presentation.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Roles',
+ 'resource://gre/modules/accessibility/Constants.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Events',
+ 'resource://gre/modules/accessibility/Constants.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'States',
+ 'resource://gre/modules/accessibility/Constants.jsm');
+
+this.EXPORTED_SYMBOLS = ['EventManager'];
+
+this.EventManager = function EventManager(aContentScope, aContentControl) {
+ this.contentScope = aContentScope;
+ this.contentControl = aContentControl;
+ this.addEventListener = this.contentScope.addEventListener.bind(
+ this.contentScope);
+ this.removeEventListener = this.contentScope.removeEventListener.bind(
+ this.contentScope);
+ this.sendMsgFunc = this.contentScope.sendAsyncMessage.bind(
+ this.contentScope);
+ this.webProgress = this.contentScope.docShell.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebProgress);
+};
+
+this.EventManager.prototype = {
+ editState: { editing: false },
+
+ start: function start() {
+ try {
+ if (!this._started) {
+ Logger.debug('EventManager.start');
+
+ this._started = true;
+
+ AccessibilityEventObserver.addListener(this);
+
+ this.webProgress.addProgressListener(this,
+ (Ci.nsIWebProgress.NOTIFY_STATE_ALL |
+ Ci.nsIWebProgress.NOTIFY_LOCATION));
+ this.addEventListener('wheel', this, true);
+ this.addEventListener('scroll', this, true);
+ this.addEventListener('resize', this, true);
+ this._preDialogPosition = new WeakMap();
+ }
+ this.present(Presentation.tabStateChanged(null, 'newtab'));
+
+ } catch (x) {
+ Logger.logException(x, 'Failed to start EventManager');
+ }
+ },
+
+ // XXX: Stop is not called when the tab is closed (|TabClose| event is too
+ // late). It is only called when the AccessFu is disabled explicitly.
+ stop: function stop() {
+ if (!this._started) {
+ return;
+ }
+ Logger.debug('EventManager.stop');
+ AccessibilityEventObserver.removeListener(this);
+ try {
+ this._preDialogPosition = new WeakMap();
+ this.webProgress.removeProgressListener(this);
+ this.removeEventListener('wheel', this, true);
+ this.removeEventListener('scroll', this, true);
+ this.removeEventListener('resize', this, true);
+ } catch (x) {
+ // contentScope is dead.
+ } finally {
+ this._started = false;
+ }
+ },
+
+ handleEvent: function handleEvent(aEvent) {
+ Logger.debug(() => {
+ return ['DOMEvent', aEvent.type];
+ });
+
+ try {
+ switch (aEvent.type) {
+ case 'wheel':
+ {
+ let attempts = 0;
+ let delta = aEvent.deltaX || aEvent.deltaY;
+ this.contentControl.autoMove(
+ null,
+ { moveMethod: delta > 0 ? 'moveNext' : 'movePrevious',
+ onScreenOnly: true, noOpIfOnScreen: true, delay: 500 });
+ break;
+ }
+ case 'scroll':
+ case 'resize':
+ {
+ // the target could be an element, document or window
+ let window = null;
+ if (aEvent.target instanceof Ci.nsIDOMWindow)
+ window = aEvent.target;
+ else if (aEvent.target instanceof Ci.nsIDOMDocument)
+ window = aEvent.target.defaultView;
+ else if (aEvent.target instanceof Ci.nsIDOMElement)
+ window = aEvent.target.ownerDocument.defaultView;
+ this.present(Presentation.viewportChanged(window));
+ break;
+ }
+ }
+ } catch (x) {
+ Logger.logException(x, 'Error handling DOM event');
+ }
+ },
+
+ handleAccEvent: function handleAccEvent(aEvent) {
+ Logger.debug(() => {
+ return ['A11yEvent', Logger.eventToString(aEvent),
+ Logger.accessibleToString(aEvent.accessible)];
+ });
+
+ // Don't bother with non-content events in firefox.
+ if (Utils.MozBuildApp == 'browser' &&
+ aEvent.eventType != Events.VIRTUALCURSOR_CHANGED &&
+ // XXX Bug 442005 results in DocAccessible::getDocType returning
+ // NS_ERROR_FAILURE. Checking for aEvent.accessibleDocument.docType ==
+ // 'window' does not currently work.
+ (aEvent.accessibleDocument.DOMDocument.doctype &&
+ aEvent.accessibleDocument.DOMDocument.doctype.name === 'window')) {
+ return;
+ }
+
+ switch (aEvent.eventType) {
+ case Events.VIRTUALCURSOR_CHANGED:
+ {
+ let pivot = aEvent.accessible.
+ QueryInterface(Ci.nsIAccessibleDocument).virtualCursor;
+ let position = pivot.position;
+ if (position && position.role == Roles.INTERNAL_FRAME)
+ break;
+ let event = aEvent.
+ QueryInterface(Ci.nsIAccessibleVirtualCursorChangeEvent);
+ let reason = event.reason;
+ let oldAccessible = event.oldAccessible;
+
+ if (this.editState.editing &&
+ !Utils.getState(position).contains(States.FOCUSED)) {
+ aEvent.accessibleDocument.takeFocus();
+ }
+ this.present(
+ Presentation.pivotChanged(position, oldAccessible, reason,
+ pivot.startOffset, pivot.endOffset,
+ aEvent.isFromUserInput));
+
+ break;
+ }
+ case Events.STATE_CHANGE:
+ {
+ let event = aEvent.QueryInterface(Ci.nsIAccessibleStateChangeEvent);
+ let state = Utils.getState(event);
+ if (state.contains(States.CHECKED)) {
+ if (aEvent.accessible.role === Roles.SWITCH) {
+ this.present(
+ Presentation.
+ actionInvoked(aEvent.accessible,
+ event.isEnabled ? 'on' : 'off'));
+ } else {
+ this.present(
+ Presentation.
+ actionInvoked(aEvent.accessible,
+ event.isEnabled ? 'check' : 'uncheck'));
+ }
+ } else if (state.contains(States.SELECTED)) {
+ this.present(
+ Presentation.
+ actionInvoked(aEvent.accessible,
+ event.isEnabled ? 'select' : 'unselect'));
+ }
+ break;
+ }
+ case Events.NAME_CHANGE:
+ {
+ let acc = aEvent.accessible;
+ if (acc === this.contentControl.vc.position) {
+ this.present(Presentation.nameChanged(acc));
+ } else {
+ let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
+ ['text', 'all']);
+ if (liveRegion) {
+ this.present(Presentation.nameChanged(acc, isPolite));
+ }
+ }
+ break;
+ }
+ case Events.SCROLLING_START:
+ {
+ this.contentControl.autoMove(aEvent.accessible);
+ break;
+ }
+ case Events.TEXT_CARET_MOVED:
+ {
+ let acc = aEvent.accessible.QueryInterface(Ci.nsIAccessibleText);
+ let caretOffset = aEvent.
+ QueryInterface(Ci.nsIAccessibleCaretMoveEvent).caretOffset;
+
+ // We could get a caret move in an accessible that is not focused,
+ // it doesn't mean we are not on any editable accessible. just not
+ // on this one..
+ let state = Utils.getState(acc);
+ if (state.contains(States.FOCUSED)) {
+ this._setEditingMode(aEvent, caretOffset);
+ if (state.contains(States.EDITABLE)) {
+ this.present(Presentation.textSelectionChanged(acc.getText(0, -1),
+ caretOffset, caretOffset, 0, 0, aEvent.isFromUserInput));
+ }
+ }
+ break;
+ }
+ case Events.OBJECT_ATTRIBUTE_CHANGED:
+ {
+ let evt = aEvent.QueryInterface(
+ Ci.nsIAccessibleObjectAttributeChangedEvent);
+ if (evt.changedAttribute.toString() !== 'aria-hidden') {
+ // Only handle aria-hidden attribute change.
+ break;
+ }
+ let hidden = Utils.isHidden(aEvent.accessible);
+ this[hidden ? '_handleHide' : '_handleShow'](evt);
+ if (this.inTest) {
+ this.sendMsgFunc("AccessFu:AriaHidden", { hidden: hidden });
+ }
+ break;
+ }
+ case Events.SHOW:
+ {
+ this._handleShow(aEvent);
+ break;
+ }
+ case Events.HIDE:
+ {
+ let evt = aEvent.QueryInterface(Ci.nsIAccessibleHideEvent);
+ this._handleHide(evt);
+ break;
+ }
+ case Events.TEXT_INSERTED:
+ case Events.TEXT_REMOVED:
+ {
+ let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
+ ['text', 'all']);
+ if (aEvent.isFromUserInput || liveRegion) {
+ // Handle all text mutations coming from the user or if they happen
+ // on a live region.
+ this._handleText(aEvent, liveRegion, isPolite);
+ }
+ break;
+ }
+ case Events.FOCUS:
+ {
+ // Put vc where the focus is at
+ let acc = aEvent.accessible;
+ let doc = aEvent.accessibleDocument;
+ this._setEditingMode(aEvent);
+ if ([Roles.CHROME_WINDOW,
+ Roles.DOCUMENT,
+ Roles.APPLICATION].indexOf(acc.role) < 0) {
+ this.contentControl.autoMove(acc);
+ }
+
+ if (this.inTest) {
+ this.sendMsgFunc("AccessFu:Focused");
+ }
+ break;
+ }
+ case Events.DOCUMENT_LOAD_COMPLETE:
+ {
+ let position = this.contentControl.vc.position;
+ // Check if position is in the subtree of the DOCUMENT_LOAD_COMPLETE
+ // event's dialog accesible or accessible document
+ let subtreeRoot = aEvent.accessible.role === Roles.DIALOG ?
+ aEvent.accessible : aEvent.accessibleDocument;
+ if (aEvent.accessible === aEvent.accessibleDocument ||
+ (position && Utils.isInSubtree(position, subtreeRoot))) {
+ // Do not automove into the document if the virtual cursor is already
+ // positioned inside it.
+ break;
+ }
+ this._preDialogPosition.set(aEvent.accessible.DOMNode, position);
+ this.contentControl.autoMove(aEvent.accessible, { delay: 500 });
+ break;
+ }
+ case Events.VALUE_CHANGE:
+ case Events.TEXT_VALUE_CHANGE:
+ {
+ let position = this.contentControl.vc.position;
+ let target = aEvent.accessible;
+ if (position === target ||
+ Utils.getEmbeddedControl(position) === target) {
+ this.present(Presentation.valueChanged(target));
+ } else {
+ let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
+ ['text', 'all']);
+ if (liveRegion) {
+ this.present(Presentation.valueChanged(target, isPolite));
+ }
+ }
+ }
+ }
+ },
+
+ _setEditingMode: function _setEditingMode(aEvent, aCaretOffset) {
+ let acc = aEvent.accessible;
+ let accText, characterCount;
+ let caretOffset = aCaretOffset;
+
+ try {
+ accText = acc.QueryInterface(Ci.nsIAccessibleText);
+ } catch (e) {
+ // No text interface on this accessible.
+ }
+
+ if (accText) {
+ characterCount = accText.characterCount;
+ if (caretOffset === undefined) {
+ caretOffset = accText.caretOffset;
+ }
+ }
+
+ // Update editing state, both for presenter and other things
+ let state = Utils.getState(acc);
+
+ let editState = {
+ editing: state.contains(States.EDITABLE) &&
+ state.contains(States.FOCUSED),
+ multiline: state.contains(States.MULTI_LINE),
+ atStart: caretOffset === 0,
+ atEnd: caretOffset === characterCount
+ };
+
+ // Not interesting
+ if (!editState.editing && editState.editing === this.editState.editing) {
+ return;
+ }
+
+ if (editState.editing !== this.editState.editing) {
+ this.present(Presentation.editingModeChanged(editState.editing));
+ }
+
+ if (editState.editing !== this.editState.editing ||
+ editState.multiline !== this.editState.multiline ||
+ editState.atEnd !== this.editState.atEnd ||
+ editState.atStart !== this.editState.atStart) {
+ this.sendMsgFunc("AccessFu:Input", editState);
+ }
+
+ this.editState = editState;
+ },
+
+ _handleShow: function _handleShow(aEvent) {
+ let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
+ ['additions', 'all']);
+ // Only handle show if it is a relevant live region.
+ if (!liveRegion) {
+ return;
+ }
+ // Show for text is handled by the EVENT_TEXT_INSERTED handler.
+ if (aEvent.accessible.role === Roles.TEXT_LEAF) {
+ return;
+ }
+ this._dequeueLiveEvent(Events.HIDE, liveRegion);
+ this.present(Presentation.liveRegion(liveRegion, isPolite, false));
+ },
+
+ _handleHide: function _handleHide(aEvent) {
+ let {liveRegion, isPolite} = this._handleLiveRegion(
+ aEvent, ['removals', 'all']);
+ let acc = aEvent.accessible;
+ if (liveRegion) {
+ // Hide for text is handled by the EVENT_TEXT_REMOVED handler.
+ if (acc.role === Roles.TEXT_LEAF) {
+ return;
+ }
+ this._queueLiveEvent(Events.HIDE, liveRegion, isPolite);
+ } else {
+ let vc = Utils.getVirtualCursor(this.contentScope.content.document);
+ if (vc.position &&
+ (Utils.getState(vc.position).contains(States.DEFUNCT) ||
+ Utils.isInSubtree(vc.position, acc))) {
+ let position = this._preDialogPosition.get(aEvent.accessible.DOMNode) ||
+ aEvent.targetPrevSibling || aEvent.targetParent;
+ if (!position) {
+ try {
+ position = acc.previousSibling;
+ } catch (x) {
+ // Accessible is unattached from the accessible tree.
+ position = acc.parent;
+ }
+ }
+ this.contentControl.autoMove(position,
+ { moveToFocused: true, delay: 500 });
+ }
+ }
+ },
+
+ _handleText: function _handleText(aEvent, aLiveRegion, aIsPolite) {
+ let event = aEvent.QueryInterface(Ci.nsIAccessibleTextChangeEvent);
+ let isInserted = event.isInserted;
+ let txtIface = aEvent.accessible.QueryInterface(Ci.nsIAccessibleText);
+
+ let text = '';
+ try {
+ text = txtIface.getText(0, Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT);
+ } catch (x) {
+ // XXX we might have gotten an exception with of a
+ // zero-length text. If we did, ignore it (bug #749810).
+ if (txtIface.characterCount) {
+ throw x;
+ }
+ }
+ // If there are embedded objects in the text, ignore them.
+ // Assuming changes to the descendants would already be handled by the
+ // show/hide event.
+ let modifiedText = event.modifiedText.replace(/\uFFFC/g, '');
+ if (modifiedText != event.modifiedText && !modifiedText.trim()) {
+ return;
+ }
+
+ if (aLiveRegion) {
+ if (aEvent.eventType === Events.TEXT_REMOVED) {
+ this._queueLiveEvent(Events.TEXT_REMOVED, aLiveRegion, aIsPolite,
+ modifiedText);
+ } else {
+ this._dequeueLiveEvent(Events.TEXT_REMOVED, aLiveRegion);
+ this.present(Presentation.liveRegion(aLiveRegion, aIsPolite, false,
+ modifiedText));
+ }
+ } else {
+ this.present(Presentation.textChanged(aEvent.accessible, isInserted,
+ event.start, event.length, text, modifiedText));
+ }
+ },
+
+ _handleLiveRegion: function _handleLiveRegion(aEvent, aRelevant) {
+ if (aEvent.isFromUserInput) {
+ return {};
+ }
+ let parseLiveAttrs = function parseLiveAttrs(aAccessible) {
+ let attrs = Utils.getAttributes(aAccessible);
+ if (attrs['container-live']) {
+ return {
+ live: attrs['container-live'],
+ relevant: attrs['container-relevant'] || 'additions text',
+ busy: attrs['container-busy'],
+ atomic: attrs['container-atomic'],
+ memberOf: attrs['member-of']
+ };
+ }
+ return null;
+ };
+ // XXX live attributes are not set for hidden accessibles yet. Need to
+ // climb up the tree to check for them.
+ let getLiveAttributes = function getLiveAttributes(aEvent) {
+ let liveAttrs = parseLiveAttrs(aEvent.accessible);
+ if (liveAttrs) {
+ return liveAttrs;
+ }
+ let parent = aEvent.targetParent;
+ while (parent) {
+ liveAttrs = parseLiveAttrs(parent);
+ if (liveAttrs) {
+ return liveAttrs;
+ }
+ parent = parent.parent
+ }
+ return {};
+ };
+ let {live, relevant, busy, atomic, memberOf} = getLiveAttributes(aEvent);
+ // If container-live is not present or is set to |off| ignore the event.
+ if (!live || live === 'off') {
+ return {};
+ }
+ // XXX: support busy and atomic.
+
+ // Determine if the type of the mutation is relevant. Default is additions
+ // and text.
+ let isRelevant = Utils.matchAttributeValue(relevant, aRelevant);
+ if (!isRelevant) {
+ return {};
+ }
+ return {
+ liveRegion: aEvent.accessible,
+ isPolite: live === 'polite'
+ };
+ },
+
+ _dequeueLiveEvent: function _dequeueLiveEvent(aEventType, aLiveRegion) {
+ let domNode = aLiveRegion.DOMNode;
+ if (this._liveEventQueue && this._liveEventQueue.has(domNode)) {
+ let queue = this._liveEventQueue.get(domNode);
+ let nextEvent = queue[0];
+ if (nextEvent.eventType === aEventType) {
+ Utils.win.clearTimeout(nextEvent.timeoutID);
+ queue.shift();
+ if (queue.length === 0) {
+ this._liveEventQueue.delete(domNode)
+ }
+ }
+ }
+ },
+
+ _queueLiveEvent: function _queueLiveEvent(aEventType, aLiveRegion, aIsPolite, aModifiedText) {
+ if (!this._liveEventQueue) {
+ this._liveEventQueue = new WeakMap();
+ }
+ let eventHandler = {
+ eventType: aEventType,
+ timeoutID: Utils.win.setTimeout(this.present.bind(this),
+ 20, // Wait for a possible EVENT_SHOW or EVENT_TEXT_INSERTED event.
+ Presentation.liveRegion(aLiveRegion, aIsPolite, true, aModifiedText))
+ };
+
+ let domNode = aLiveRegion.DOMNode;
+ if (this._liveEventQueue.has(domNode)) {
+ this._liveEventQueue.get(domNode).push(eventHandler);
+ } else {
+ this._liveEventQueue.set(domNode, [eventHandler]);
+ }
+ },
+
+ present: function present(aPresentationData) {
+ this.sendMsgFunc("AccessFu:Present", aPresentationData);
+ },
+
+ onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ let tabstate = '';
+
+ let loadingState = Ci.nsIWebProgressListener.STATE_TRANSFERRING |
+ Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
+ let loadedState = Ci.nsIWebProgressListener.STATE_STOP |
+ Ci.nsIWebProgressListener.STATE_IS_NETWORK;
+
+ if ((aStateFlags & loadingState) == loadingState) {
+ tabstate = 'loading';
+ } else if ((aStateFlags & loadedState) == loadedState &&
+ !aWebProgress.isLoadingDocument) {
+ tabstate = 'loaded';
+ }
+
+ if (tabstate) {
+ let docAcc = Utils.AccService.getAccessibleFor(aWebProgress.DOMWindow.document);
+ this.present(Presentation.tabStateChanged(docAcc, tabstate));
+ }
+ },
+
+ onProgressChange: function onProgressChange() {},
+
+ onLocationChange: function onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+ let docAcc = Utils.AccService.getAccessibleFor(aWebProgress.DOMWindow.document);
+ this.present(Presentation.tabStateChanged(docAcc, 'newdoc'));
+ },
+
+ onStatusChange: function onStatusChange() {},
+
+ onSecurityChange: function onSecurityChange() {},
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference,
+ Ci.nsISupports,
+ Ci.nsIObserver])
+};
+
+const AccessibilityEventObserver = {
+
+ /**
+ * A WeakMap containing [content, EventManager] pairs.
+ */
+ eventManagers: new WeakMap(),
+
+ /**
+ * A total number of registered eventManagers.
+ */
+ listenerCount: 0,
+
+ /**
+ * An indicator of an active 'accessible-event' observer.
+ */
+ started: false,
+
+ /**
+ * Start an AccessibilityEventObserver.
+ */
+ start: function start() {
+ if (this.started || this.listenerCount === 0) {
+ return;
+ }
+ Services.obs.addObserver(this, 'accessible-event', false);
+ this.started = true;
+ },
+
+ /**
+ * Stop an AccessibilityEventObserver.
+ */
+ stop: function stop() {
+ if (!this.started) {
+ return;
+ }
+ Services.obs.removeObserver(this, 'accessible-event');
+ // Clean up all registered event managers.
+ this.eventManagers = new WeakMap();
+ this.listenerCount = 0;
+ this.started = false;
+ },
+
+ /**
+ * Register an EventManager and start listening to the
+ * 'accessible-event' messages.
+ *
+ * @param aEventManager EventManager
+ * An EventManager object that was loaded into the specific content.
+ */
+ addListener: function addListener(aEventManager) {
+ let content = aEventManager.contentScope.content;
+ if (!this.eventManagers.has(content)) {
+ this.listenerCount++;
+ }
+ this.eventManagers.set(content, aEventManager);
+ // Since at least one EventManager was registered, start listening.
+ Logger.debug('AccessibilityEventObserver.addListener. Total:',
+ this.listenerCount);
+ this.start();
+ },
+
+ /**
+ * Unregister an EventManager and, optionally, stop listening to the
+ * 'accessible-event' messages.
+ *
+ * @param aEventManager EventManager
+ * An EventManager object that was stopped in the specific content.
+ */
+ removeListener: function removeListener(aEventManager) {
+ let content = aEventManager.contentScope.content;
+ if (!this.eventManagers.delete(content)) {
+ return;
+ }
+ this.listenerCount--;
+ Logger.debug('AccessibilityEventObserver.removeListener. Total:',
+ this.listenerCount);
+ if (this.listenerCount === 0) {
+ // If there are no EventManagers registered at the moment, stop listening
+ // to the 'accessible-event' messages.
+ this.stop();
+ }
+ },
+
+ /**
+ * Lookup an EventManager for a specific content. If the EventManager is not
+ * found, walk up the hierarchy of parent windows.
+ * @param content Window
+ * A content Window used to lookup the corresponding EventManager.
+ */
+ getListener: function getListener(content) {
+ let eventManager = this.eventManagers.get(content);
+ if (eventManager) {
+ return eventManager;
+ }
+ let parent = content.parent;
+ if (parent === content) {
+ // There is no parent or the parent is of a different type.
+ return null;
+ }
+ return this.getListener(parent);
+ },
+
+ /**
+ * Handle the 'accessible-event' message.
+ */
+ observe: function observe(aSubject, aTopic, aData) {
+ if (aTopic !== 'accessible-event') {
+ return;
+ }
+ let event = aSubject.QueryInterface(Ci.nsIAccessibleEvent);
+ if (!event.accessibleDocument) {
+ Logger.warning(
+ 'AccessibilityEventObserver.observe: no accessible document:',
+ Logger.eventToString(event), "accessible:",
+ Logger.accessibleToString(event.accessible));
+ return;
+ }
+ let content = event.accessibleDocument.window;
+ // Match the content window to its EventManager.
+ let eventManager = this.getListener(content);
+ if (!eventManager || !eventManager._started) {
+ if (Utils.MozBuildApp === 'browser' &&
+ !(content instanceof Ci.nsIDOMChromeWindow)) {
+ Logger.warning(
+ 'AccessibilityEventObserver.observe: ignored event:',
+ Logger.eventToString(event), "accessible:",
+ Logger.accessibleToString(event.accessible), "document:",
+ Logger.accessibleToString(event.accessibleDocument));
+ }
+ return;
+ }
+ try {
+ eventManager.handleAccEvent(event);
+ } catch (x) {
+ Logger.logException(x, 'Error handing accessible event');
+ } finally {
+ return;
+ }
+ }
+};
diff --git a/accessible/jsat/Gestures.jsm b/accessible/jsat/Gestures.jsm
new file mode 100644
index 000000000..cc431614c
--- /dev/null
+++ b/accessible/jsat/Gestures.jsm
@@ -0,0 +1,956 @@
+/* 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/. */
+
+/* global Components, GestureSettings, XPCOMUtils, Utils, Promise, Logger */
+/* exported GestureSettings, GestureTracker */
+
+/******************************************************************************
+ All gestures have the following pathways when being resolved(v)/rejected(x):
+ Tap -> DoubleTap (x)
+ -> Dwell (x)
+ -> Swipe (x)
+
+ DoubleTap -> TripleTap (x)
+ -> TapHold (x)
+
+ TripleTap -> DoubleTapHold (x)
+
+ Dwell -> DwellEnd (v)
+
+ Swipe -> Explore (x)
+
+ TapHold -> TapHoldEnd (v)
+
+ DoubleTapHold -> DoubleTapHoldEnd (v)
+
+ DwellEnd -> Explore (x)
+
+ TapHoldEnd -> Explore (x)
+
+ DoubleTapHoldEnd -> Explore (x)
+
+ ExploreEnd -> Explore (x)
+
+ Explore -> ExploreEnd (v)
+******************************************************************************/
+
+'use strict';
+
+const Cu = Components.utils;
+
+this.EXPORTED_SYMBOLS = ['GestureSettings', 'GestureTracker']; // jshint ignore:line
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+
+XPCOMUtils.defineLazyModuleGetter(this, 'Utils', // jshint ignore:line
+ 'resource://gre/modules/accessibility/Utils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Logger', // jshint ignore:line
+ 'resource://gre/modules/accessibility/Utils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'setTimeout', // jshint ignore:line
+ 'resource://gre/modules/Timer.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'clearTimeout', // jshint ignore:line
+ 'resource://gre/modules/Timer.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Promise', // jshint ignore:line
+ 'resource://gre/modules/Promise.jsm');
+
+// Default maximum duration of swipe
+const SWIPE_MAX_DURATION = 200;
+// Default maximum amount of time allowed for a gesture to be considered a
+// multitouch
+const MAX_MULTITOUCH = 125;
+// Default maximum consecutive pointer event timeout
+const MAX_CONSECUTIVE_GESTURE_DELAY = 200;
+// Default delay before tap turns into dwell
+const DWELL_THRESHOLD = 250;
+// Minimal swipe distance in inches
+const SWIPE_MIN_DISTANCE = 0.4;
+// Maximum distance the pointer could move during a tap in inches
+const TAP_MAX_RADIUS = 0.2;
+// Directness coefficient. It is based on the maximum 15 degree angle between
+// consequent pointer move lines.
+const DIRECTNESS_COEFF = 1.44;
+// Amount in inches from the edges of the screen for it to be an edge swipe
+const EDGE = 0.1;
+// Multiply timeouts by this constant, x2 works great too for slower users.
+const TIMEOUT_MULTIPLIER = 1;
+// A single pointer down/up sequence periodically precedes the tripple swipe
+// gesture on Android. This delay acounts for that.
+const IS_ANDROID = Utils.MozBuildApp === 'mobile/android' &&
+ Utils.AndroidSdkVersion >= 14;
+
+/**
+ * A point object containing distance travelled data.
+ * @param {Object} aPoint A point object that looks like: {
+ * x: x coordinate in pixels,
+ * y: y coordinate in pixels
+ * }
+ */
+function Point(aPoint) {
+ this.startX = this.x = aPoint.x;
+ this.startY = this.y = aPoint.y;
+ this.distanceTraveled = 0;
+ this.totalDistanceTraveled = 0;
+}
+
+Point.prototype = {
+ /**
+ * Update the current point coordiates.
+ * @param {Object} aPoint A new point coordinates.
+ */
+ update: function Point_update(aPoint) {
+ let lastX = this.x;
+ let lastY = this.y;
+ this.x = aPoint.x;
+ this.y = aPoint.y;
+ this.distanceTraveled = this.getDistanceToCoord(lastX, lastY);
+ this.totalDistanceTraveled += this.distanceTraveled;
+ },
+
+ reset: function Point_reset() {
+ this.distanceTraveled = 0;
+ this.totalDistanceTraveled = 0;
+ },
+
+ /**
+ * Get distance between the current point coordinates and the given ones.
+ * @param {Number} aX A pixel value for the x coordinate.
+ * @param {Number} aY A pixel value for the y coordinate.
+ * @return {Number} A distance between point's current and the given
+ * coordinates.
+ */
+ getDistanceToCoord: function Point_getDistanceToCoord(aX, aY) {
+ return Math.hypot(this.x - aX, this.y - aY);
+ },
+
+ /**
+ * Get the direct distance travelled by the point so far.
+ */
+ get directDistanceTraveled() {
+ return this.getDistanceToCoord(this.startX, this.startY);
+ }
+};
+
+/**
+ * An externally accessible collection of settings used in gesture resolition.
+ * @type {Object}
+ */
+this.GestureSettings = { // jshint ignore:line
+ /**
+ * Maximum duration of swipe
+ * @type {Number}
+ */
+ swipeMaxDuration: SWIPE_MAX_DURATION * TIMEOUT_MULTIPLIER,
+
+ /**
+ * Maximum amount of time allowed for a gesture to be considered a multitouch.
+ * @type {Number}
+ */
+ maxMultitouch: MAX_MULTITOUCH * TIMEOUT_MULTIPLIER,
+
+ /**
+ * Maximum consecutive pointer event timeout.
+ * @type {Number}
+ */
+ maxConsecutiveGestureDelay:
+ MAX_CONSECUTIVE_GESTURE_DELAY * TIMEOUT_MULTIPLIER,
+
+ /**
+ * A maximum time we wait for a next pointer down event to consider a sequence
+ * a multi-action gesture.
+ * @type {Number}
+ */
+ maxGestureResolveTimeout:
+ MAX_CONSECUTIVE_GESTURE_DELAY * TIMEOUT_MULTIPLIER,
+
+ /**
+ * Delay before tap turns into dwell
+ * @type {Number}
+ */
+ dwellThreshold: DWELL_THRESHOLD * TIMEOUT_MULTIPLIER,
+
+ /**
+ * Minimum distance that needs to be travelled for the pointer move to be
+ * fired.
+ * @type {Number}
+ */
+ travelThreshold: 0.025
+};
+
+/**
+ * An interface that handles the pointer events and calculates the appropriate
+ * gestures.
+ * @type {Object}
+ */
+this.GestureTracker = { // jshint ignore:line
+ /**
+ * Reset GestureTracker to its initial state.
+ * @return {[type]} [description]
+ */
+ reset: function GestureTracker_reset() {
+ if (this.current) {
+ this.current.clearTimer();
+ }
+ delete this.current;
+ },
+
+ /**
+ * Create a new gesture object and attach resolution handler to it as well as
+ * handle the incoming pointer event.
+ * @param {Object} aDetail A new pointer event detail.
+ * @param {Number} aTimeStamp A new pointer event timeStamp.
+ * @param {Function} aGesture A gesture constructor (default: Tap).
+ */
+ _init: function GestureTracker__init(aDetail, aTimeStamp, aGesture) {
+ // Only create a new gesture on |pointerdown| event.
+ if (aDetail.type !== 'pointerdown') {
+ return;
+ }
+ let GestureConstructor = aGesture || (IS_ANDROID ? DoubleTap : Tap);
+ this._create(GestureConstructor);
+ this._update(aDetail, aTimeStamp);
+ },
+
+ /**
+ * Handle the incoming pointer event with the existing gesture object(if
+ * present) or with the newly created one.
+ * @param {Object} aDetail A new pointer event detail.
+ * @param {Number} aTimeStamp A new pointer event timeStamp.
+ */
+ handle: function GestureTracker_handle(aDetail, aTimeStamp) {
+ Logger.gesture(() => {
+ return ['Pointer event', Utils.dpi, 'at:', aTimeStamp, JSON.stringify(aDetail)];
+ });
+ this[this.current ? '_update' : '_init'](aDetail, aTimeStamp);
+ },
+
+ /**
+ * Create a new gesture object and attach resolution handler to it.
+ * @param {Function} aGesture A gesture constructor.
+ * @param {Number} aTimeStamp An original pointer event timeStamp.
+ * @param {Array} aPoints All changed points associated with the new pointer
+ * event.
+ * @param {?String} aLastEvent Last pointer event type.
+ */
+ _create: function GestureTracker__create(aGesture, aTimeStamp, aPoints, aLastEvent) {
+ this.current = new aGesture(aTimeStamp, aPoints, aLastEvent); /* A constructor name should start with an uppercase letter. */ // jshint ignore:line
+ this.current.then(this._onFulfill.bind(this));
+ },
+
+ /**
+ * Handle the incoming pointer event with the existing gesture object.
+ * @param {Object} aDetail A new pointer event detail.
+ * @param {Number} aTimeStamp A new pointer event timeStamp.
+ */
+ _update: function GestureTracker_update(aDetail, aTimeStamp) {
+ this.current[aDetail.type](aDetail.points, aTimeStamp);
+ },
+
+ /**
+ * A resolution handler function for the current gesture promise.
+ * @param {Object} aResult A resolution payload with the relevant gesture id
+ * and an optional new gesture contructor.
+ */
+ _onFulfill: function GestureTracker__onFulfill(aResult) {
+ let {id, gestureType} = aResult;
+ let current = this.current;
+ // Do nothing if there's no existing gesture or there's already a newer
+ // gesture.
+ if (!current || current.id !== id) {
+ return;
+ }
+ // Only create a gesture if we got a constructor.
+ if (gestureType) {
+ this._create(gestureType, current.startTime, current.points,
+ current.lastEvent);
+ } else {
+ this.current.clearTimer();
+ delete this.current;
+ }
+ }
+};
+
+/**
+ * Compile a mozAccessFuGesture detail structure.
+ * @param {String} aType A gesture type.
+ * @param {Object} aPoints Gesture's points.
+ * @param {String} xKey A default key for the x coordinate. Default is
+ * 'startX'.
+ * @param {String} yKey A default key for the y coordinate. Default is
+ * 'startY'.
+ * @return {Object} a mozAccessFuGesture detail structure.
+ */
+function compileDetail(aType, aPoints, keyMap = {x: 'startX', y: 'startY'}) {
+ let touches = [];
+ let maxDeltaX = 0;
+ let maxDeltaY = 0;
+ for (let identifier in aPoints) {
+ let point = aPoints[identifier];
+ let touch = {};
+ for (let key in keyMap) {
+ touch[key] = point[keyMap[key]];
+ }
+ touches.push(touch);
+ let deltaX = point.x - point.startX;
+ let deltaY = point.y - point.startY;
+ // Determine the maximum x and y travel intervals.
+ if (Math.abs(maxDeltaX) < Math.abs(deltaX)) {
+ maxDeltaX = deltaX;
+ }
+ if (Math.abs(maxDeltaY) < Math.abs(deltaY)) {
+ maxDeltaY = deltaY;
+ }
+ // Since the gesture is resolving, reset the points' distance information
+ // since they are passed to the next potential gesture.
+ point.reset();
+ }
+ return {
+ type: aType,
+ touches: touches,
+ deltaX: maxDeltaX,
+ deltaY: maxDeltaY
+ };
+}
+
+/**
+ * A general gesture object.
+ * @param {Number} aTimeStamp An original pointer event's timeStamp that started
+ * the gesture resolution sequence.
+ * @param {Object} aPoints An existing set of points (from previous events).
+ * Default is an empty object.
+ * @param {?String} aLastEvent Last pointer event type.
+ */
+function Gesture(aTimeStamp, aPoints = {}, aLastEvent = undefined) {
+ this.startTime = Date.now();
+ Logger.gesture('Creating', this.id, 'gesture.');
+ this.points = aPoints;
+ this.lastEvent = aLastEvent;
+ this._deferred = Promise.defer();
+ // Call this._handleResolve or this._handleReject when the promise is
+ // fulfilled with either resolve or reject.
+ this.promise = this._deferred.promise.then(this._handleResolve.bind(this),
+ this._handleReject.bind(this));
+ this.startTimer(aTimeStamp);
+}
+
+Gesture.prototype = {
+ /**
+ * Get the gesture timeout delay.
+ * @return {Number}
+ */
+ _getDelay: function Gesture__getDelay() {
+ // If nothing happens withing the
+ // GestureSettings.maxConsecutiveGestureDelay, we should not wait for any
+ // more pointer events and consider them the part of the same gesture -
+ // reject this gesture promise.
+ return GestureSettings.maxConsecutiveGestureDelay;
+ },
+
+ /**
+ * Clear the existing timer.
+ */
+ clearTimer: function Gesture_clearTimer() {
+ Logger.gesture('clearTimeout', this.type);
+ clearTimeout(this._timer);
+ delete this._timer;
+ },
+
+ /**
+ * Start the timer for gesture timeout.
+ * @param {Number} aTimeStamp An original pointer event's timeStamp that
+ * started the gesture resolution sequence.
+ */
+ startTimer: function Gesture_startTimer(aTimeStamp) {
+ Logger.gesture('startTimer', this.type);
+ this.clearTimer();
+ let delay = this._getDelay(aTimeStamp);
+ let handler = () => {
+ Logger.gesture('timer handler');
+ this.clearTimer();
+ if (!this._inProgress) {
+ this._deferred.reject();
+ } else if (this._rejectToOnWait) {
+ this._deferred.reject(this._rejectToOnWait);
+ }
+ };
+ if (delay <= 0) {
+ handler();
+ } else {
+ this._timer = setTimeout(handler, delay);
+ }
+ },
+
+ /**
+ * Add a gesture promise resolution callback.
+ * @param {Function} aCallback
+ */
+ then: function Gesture_then(aCallback) {
+ this.promise.then(aCallback);
+ },
+
+ /**
+ * Update gesture's points. Test the points set with the optional gesture test
+ * function.
+ * @param {Array} aPoints An array with the changed points from the new
+ * pointer event.
+ * @param {String} aType Pointer event type.
+ * @param {Boolean} aCanCreate A flag that enables including the new points.
+ * Default is false.
+ * @param {Boolean} aNeedComplete A flag that indicates that the gesture is
+ * completing. Default is false.
+ * @return {Boolean} Indicates whether the gesture can be complete (it is
+ * set to true iff the aNeedComplete is true and there was a change to at
+ * least one point that belongs to the gesture).
+ */
+ _update: function Gesture__update(aPoints, aType, aCanCreate = false, aNeedComplete = false) {
+ let complete;
+ let lastEvent;
+ for (let point of aPoints) {
+ let identifier = point.identifier;
+ let gesturePoint = this.points[identifier];
+ if (gesturePoint) {
+ if (aType === 'pointerdown' && aCanCreate) {
+ // scratch the previous pointer with that id.
+ this.points[identifier] = new Point(point);
+ } else {
+ gesturePoint.update(point);
+ }
+ if (aNeedComplete) {
+ // Since the gesture is completing and at least one of the gesture
+ // points is updated, set the return value to true.
+ complete = true;
+ }
+ lastEvent = lastEvent || aType;
+ } else if (aCanCreate) {
+ // Only create a new point if aCanCreate is true.
+ this.points[identifier] =
+ new Point(point);
+ lastEvent = lastEvent || aType;
+ }
+ }
+ this.lastEvent = lastEvent || this.lastEvent;
+ // If test function is defined test the points.
+ if (this.test) {
+ this.test(complete);
+ }
+ return complete;
+ },
+
+ /**
+ * Emit a mozAccessFuGesture (when the gesture is resolved).
+ * @param {Object} aDetail a compiled mozAccessFuGesture detail structure.
+ */
+ _emit: function Gesture__emit(aDetail) {
+ let evt = new Utils.win.CustomEvent('mozAccessFuGesture', {
+ bubbles: true,
+ cancelable: true,
+ detail: aDetail
+ });
+ Utils.win.dispatchEvent(evt);
+ },
+
+ /**
+ * Handle the pointer down event.
+ * @param {Array} aPoints A new pointer down points.
+ * @param {Number} aTimeStamp A new pointer down timeStamp.
+ */
+ pointerdown: function Gesture_pointerdown(aPoints, aTimeStamp) {
+ this._inProgress = true;
+ this._update(aPoints, 'pointerdown',
+ aTimeStamp - this.startTime < GestureSettings.maxMultitouch);
+ },
+
+ /**
+ * Handle the pointer move event.
+ * @param {Array} aPoints A new pointer move points.
+ */
+ pointermove: function Gesture_pointermove(aPoints) {
+ this._update(aPoints, 'pointermove');
+ },
+
+ /**
+ * Handle the pointer up event.
+ * @param {Array} aPoints A new pointer up points.
+ */
+ pointerup: function Gesture_pointerup(aPoints) {
+ let complete = this._update(aPoints, 'pointerup', false, true);
+ if (complete) {
+ this._deferred.resolve();
+ }
+ },
+
+ /**
+ * A subsequent gesture constructor to resolve the current one to. E.g.
+ * tap->doubletap, dwell->dwellend, etc.
+ * @type {Function}
+ */
+ resolveTo: null,
+
+ /**
+ * A unique id for the gesture. Composed of the type + timeStamp.
+ */
+ get id() {
+ delete this._id;
+ this._id = this.type + this.startTime;
+ return this._id;
+ },
+
+ /**
+ * A gesture promise resolve callback. Compile and emit the gesture.
+ * @return {Object} Returns a structure to the gesture handler that looks like
+ * this: {
+ * id: current gesture id,
+ * gestureType: an optional subsequent gesture constructor.
+ * }
+ */
+ _handleResolve: function Gesture__handleResolve() {
+ if (this.isComplete) {
+ return;
+ }
+ Logger.gesture('Resolving', this.id, 'gesture.');
+ this.isComplete = true;
+ this.clearTimer();
+ let detail = this.compile();
+ if (detail) {
+ this._emit(detail);
+ }
+ return {
+ id: this.id,
+ gestureType: this.resolveTo
+ };
+ },
+
+ /**
+ * A gesture promise reject callback.
+ * @return {Object} Returns a structure to the gesture handler that looks like
+ * this: {
+ * id: current gesture id,
+ * gestureType: an optional subsequent gesture constructor.
+ * }
+ */
+ _handleReject: function Gesture__handleReject(aRejectTo) {
+ if (this.isComplete) {
+ return;
+ }
+ Logger.gesture('Rejecting', this.id, 'gesture.');
+ this.isComplete = true;
+ this.clearTimer();
+ return {
+ id: this.id,
+ gestureType: aRejectTo
+ };
+ },
+
+ /**
+ * A default compilation function used to build the mozAccessFuGesture event
+ * detail. The detail always includes the type and the touches associated
+ * with the gesture.
+ * @return {Object} Gesture event detail.
+ */
+ compile: function Gesture_compile() {
+ return compileDetail(this.type, this.points);
+ }
+};
+
+/**
+ * A mixin for an explore related object.
+ */
+function ExploreGesture() {
+ this.compile = () => {
+ // Unlike most of other gestures explore based gestures compile using the
+ // current point position and not the start one.
+ return compileDetail(this.type, this.points, {x: 'x', y: 'y'});
+ };
+}
+
+/**
+ * Check the in progress gesture for completion.
+ */
+function checkProgressGesture(aGesture) {
+ aGesture._inProgress = true;
+ if (aGesture.lastEvent === 'pointerup') {
+ if (aGesture.test) {
+ aGesture.test(true);
+ }
+ aGesture._deferred.resolve();
+ }
+}
+
+/**
+ * A common travel gesture. When the travel gesture is created, all subsequent
+ * pointer events' points are tested for their total distance traveled. If that
+ * distance exceeds the _threshold distance, the gesture will be rejected to a
+ * _travelTo gesture.
+ * @param {Number} aTimeStamp An original pointer event's timeStamp that started
+ * the gesture resolution sequence.
+ * @param {Object} aPoints An existing set of points (from previous events).
+ * @param {?String} aLastEvent Last pointer event type.
+ * @param {Function} aTravelTo A contructor for the gesture to reject to when
+ * travelling (default: Explore).
+ * @param {Number} aThreshold Travel threshold (default:
+ * GestureSettings.travelThreshold).
+ */
+function TravelGesture(aTimeStamp, aPoints, aLastEvent, aTravelTo = Explore, aThreshold = GestureSettings.travelThreshold) {
+ Gesture.call(this, aTimeStamp, aPoints, aLastEvent);
+ this._travelTo = aTravelTo;
+ this._threshold = aThreshold;
+}
+
+TravelGesture.prototype = Object.create(Gesture.prototype);
+
+/**
+ * Test the gesture points for travel. The gesture will be rejected to
+ * this._travelTo gesture iff at least one point crosses this._threshold.
+ */
+TravelGesture.prototype.test = function TravelGesture_test() {
+ if (!this._travelTo) {
+ return;
+ }
+ for (let identifier in this.points) {
+ let point = this.points[identifier];
+ if (point.totalDistanceTraveled / Utils.dpi > this._threshold) {
+ this._deferred.reject(this._travelTo);
+ return;
+ }
+ }
+};
+
+/**
+ * DwellEnd gesture.
+ * @param {Number} aTimeStamp An original pointer event's timeStamp that started
+ * the gesture resolution sequence.
+ * @param {Object} aPoints An existing set of points (from previous events).
+ * @param {?String} aLastEvent Last pointer event type.
+ */
+function DwellEnd(aTimeStamp, aPoints, aLastEvent) {
+ this._inProgress = true;
+ // If the pointer travels, reject to Explore.
+ TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent);
+ checkProgressGesture(this);
+}
+
+DwellEnd.prototype = Object.create(TravelGesture.prototype);
+DwellEnd.prototype.type = 'dwellend';
+
+/**
+ * TapHoldEnd gesture. This gesture can be represented as the following diagram:
+ * pointerdown-pointerup-pointerdown-*wait*-pointerup.
+ * @param {Number} aTimeStamp An original pointer event's timeStamp that started
+ * the gesture resolution sequence.
+ * @param {Object} aPoints An existing set of points (from previous events).
+ * @param {?String} aLastEvent Last pointer event type.
+ */
+function TapHoldEnd(aTimeStamp, aPoints, aLastEvent) {
+ this._inProgress = true;
+ // If the pointer travels, reject to Explore.
+ TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent);
+ checkProgressGesture(this);
+}
+
+TapHoldEnd.prototype = Object.create(TravelGesture.prototype);
+TapHoldEnd.prototype.type = 'tapholdend';
+
+/**
+ * DoubleTapHoldEnd gesture. This gesture can be represented as the following
+ * diagram:
+ * pointerdown-pointerup-pointerdown-pointerup-pointerdown-*wait*-pointerup.
+ * @param {Number} aTimeStamp An original pointer event's timeStamp that started
+ * the gesture resolution sequence.
+ * @param {Object} aPoints An existing set of points (from previous events).
+ * @param {?String} aLastEvent Last pointer event type.
+ */
+function DoubleTapHoldEnd(aTimeStamp, aPoints, aLastEvent) {
+ this._inProgress = true;
+ // If the pointer travels, reject to Explore.
+ TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent);
+ checkProgressGesture(this);
+}
+
+DoubleTapHoldEnd.prototype = Object.create(TravelGesture.prototype);
+DoubleTapHoldEnd.prototype.type = 'doubletapholdend';
+
+/**
+ * A common tap gesture object.
+ * @param {Number} aTimeStamp An original pointer event's timeStamp that started
+ * the gesture resolution sequence.
+ * @param {Object} aPoints An existing set of points (from previous events).
+ * @param {?String} aLastEvent Last pointer event type.
+ * @param {Function} aRejectToOnWait A constructor for the next gesture to
+ * reject to in case no pointermove or pointerup happens within the
+ * GestureSettings.dwellThreshold.
+ * @param {Function} aTravelTo An optional constuctor for the next gesture to
+ * reject to in case the the TravelGesture test fails.
+ * @param {Function} aRejectToOnPointerDown A constructor for the gesture to
+ * reject to if a finger comes down immediately after the tap.
+ */
+function TapGesture(aTimeStamp, aPoints, aLastEvent, aRejectToOnWait, aTravelTo, aRejectToOnPointerDown) {
+ this._rejectToOnWait = aRejectToOnWait;
+ this._rejectToOnPointerDown = aRejectToOnPointerDown;
+ // If the pointer travels, reject to aTravelTo.
+ TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent, aTravelTo,
+ TAP_MAX_RADIUS);
+}
+
+TapGesture.prototype = Object.create(TravelGesture.prototype);
+TapGesture.prototype._getDelay = function TapGesture__getDelay() {
+ // If, for TapGesture, no pointermove or pointerup happens within the
+ // GestureSettings.dwellThreshold, reject.
+ // Note: the original pointer event's timeStamp is irrelevant here.
+ return GestureSettings.dwellThreshold;
+};
+
+TapGesture.prototype.pointerup = function TapGesture_pointerup(aPoints) {
+ if (this._rejectToOnPointerDown) {
+ let complete = this._update(aPoints, 'pointerup', false, true);
+ if (complete) {
+ this.clearTimer();
+ if (GestureSettings.maxGestureResolveTimeout) {
+ this._pointerUpTimer = setTimeout(() => {
+ clearTimeout(this._pointerUpTimer);
+ delete this._pointerUpTimer;
+ this._deferred.resolve();
+ }, GestureSettings.maxGestureResolveTimeout);
+ } else {
+ this._deferred.resolve();
+ }
+ }
+ } else {
+ TravelGesture.prototype.pointerup.call(this, aPoints);
+ }
+};
+
+TapGesture.prototype.pointerdown = function TapGesture_pointerdown(aPoints, aTimeStamp) {
+ if (this._pointerUpTimer) {
+ clearTimeout(this._pointerUpTimer);
+ delete this._pointerUpTimer;
+ this._deferred.reject(this._rejectToOnPointerDown);
+ } else {
+ TravelGesture.prototype.pointerdown.call(this, aPoints, aTimeStamp);
+ }
+};
+
+
+/**
+ * Tap gesture.
+ * @param {Number} aTimeStamp An original pointer event's timeStamp that started
+ * the gesture resolution sequence.
+ * @param {Object} aPoints An existing set of points (from previous events).
+ * @param {?String} aLastEvent Last pointer event type.
+ */
+function Tap(aTimeStamp, aPoints, aLastEvent) {
+ // If the pointer travels, reject to Swipe.
+ TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, Dwell, Swipe, DoubleTap);
+}
+
+Tap.prototype = Object.create(TapGesture.prototype);
+Tap.prototype.type = 'tap';
+
+
+/**
+ * Double Tap gesture.
+ * @param {Number} aTimeStamp An original pointer event's timeStamp that started
+ * the gesture resolution sequence.
+ * @param {Object} aPoints An existing set of points (from previous events).
+ * @param {?String} aLastEvent Last pointer event type.
+ */
+function DoubleTap(aTimeStamp, aPoints, aLastEvent) {
+ this._inProgress = true;
+ TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, TapHold, null, TripleTap);
+}
+
+DoubleTap.prototype = Object.create(TapGesture.prototype);
+DoubleTap.prototype.type = 'doubletap';
+
+/**
+ * Triple Tap gesture.
+ * @param {Number} aTimeStamp An original pointer event's timeStamp that started
+ * the gesture resolution sequence.
+ * @param {Object} aPoints An existing set of points (from previous events).
+ * @param {?String} aLastEvent Last pointer event type.
+ */
+function TripleTap(aTimeStamp, aPoints, aLastEvent) {
+ this._inProgress = true;
+ TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, DoubleTapHold, null, null);
+}
+
+TripleTap.prototype = Object.create(TapGesture.prototype);
+TripleTap.prototype.type = 'tripletap';
+
+/**
+ * Common base object for gestures that are created as resolved.
+ * @param {Number} aTimeStamp An original pointer event's timeStamp that started
+ * the gesture resolution sequence.
+ * @param {Object} aPoints An existing set of points (from previous events).
+ * @param {?String} aLastEvent Last pointer event type.
+ */
+function ResolvedGesture(aTimeStamp, aPoints, aLastEvent) {
+ Gesture.call(this, aTimeStamp, aPoints, aLastEvent);
+ // Resolve the guesture right away.
+ this._deferred.resolve();
+}
+
+ResolvedGesture.prototype = Object.create(Gesture.prototype);
+
+/**
+ * Dwell gesture
+ * @param {Number} aTimeStamp An original pointer event's timeStamp that started
+ * the gesture resolution sequence.
+ * @param {Object} aPoints An existing set of points (from previous events).
+ * @param {?String} aLastEvent Last pointer event type.
+ */
+function Dwell(aTimeStamp, aPoints, aLastEvent) {
+ ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent);
+}
+
+Dwell.prototype = Object.create(ResolvedGesture.prototype);
+Dwell.prototype.type = 'dwell';
+Dwell.prototype.resolveTo = DwellEnd;
+
+/**
+ * TapHold gesture
+ * @param {Number} aTimeStamp An original pointer event's timeStamp that started
+ * the gesture resolution sequence.
+ * @param {Object} aPoints An existing set of points (from previous events).
+ * @param {?String} aLastEvent Last pointer event type.
+ */
+function TapHold(aTimeStamp, aPoints, aLastEvent) {
+ ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent);
+}
+
+TapHold.prototype = Object.create(ResolvedGesture.prototype);
+TapHold.prototype.type = 'taphold';
+TapHold.prototype.resolveTo = TapHoldEnd;
+
+/**
+ * DoubleTapHold gesture
+ * @param {Number} aTimeStamp An original pointer event's timeStamp that started
+ * the gesture resolution sequence.
+ * @param {Object} aPoints An existing set of points (from previous events).
+ * @param {?String} aLastEvent Last pointer event type.
+ */
+function DoubleTapHold(aTimeStamp, aPoints, aLastEvent) {
+ ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent);
+}
+
+DoubleTapHold.prototype = Object.create(ResolvedGesture.prototype);
+DoubleTapHold.prototype.type = 'doubletaphold';
+DoubleTapHold.prototype.resolveTo = DoubleTapHoldEnd;
+
+/**
+ * Explore gesture
+ * @param {Number} aTimeStamp An original pointer event's timeStamp that started
+ * the gesture resolution sequence.
+ * @param {Object} aPoints An existing set of points (from previous events).
+ * @param {?String} aLastEvent Last pointer event type.
+ */
+function Explore(aTimeStamp, aPoints, aLastEvent) {
+ ExploreGesture.call(this);
+ ResolvedGesture.call(this, aTimeStamp, aPoints, aLastEvent);
+}
+
+Explore.prototype = Object.create(ResolvedGesture.prototype);
+Explore.prototype.type = 'explore';
+Explore.prototype.resolveTo = ExploreEnd;
+
+/**
+ * ExploreEnd gesture.
+ * @param {Number} aTimeStamp An original pointer event's timeStamp that started
+ * the gesture resolution sequence.
+ * @param {Object} aPoints An existing set of points (from previous events).
+ * @param {?String} aLastEvent Last pointer event type.
+ */
+function ExploreEnd(aTimeStamp, aPoints, aLastEvent) {
+ this._inProgress = true;
+ ExploreGesture.call(this);
+ // If the pointer travels, reject to Explore.
+ TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent);
+ checkProgressGesture(this);
+}
+
+ExploreEnd.prototype = Object.create(TravelGesture.prototype);
+ExploreEnd.prototype.type = 'exploreend';
+
+/**
+ * Swipe gesture.
+ * @param {Number} aTimeStamp An original pointer event's timeStamp that started
+ * the gesture resolution sequence.
+ * @param {Object} aPoints An existing set of points (from previous events).
+ * @param {?String} aLastEvent Last pointer event type.
+ */
+function Swipe(aTimeStamp, aPoints, aLastEvent) {
+ this._inProgress = true;
+ this._rejectToOnWait = Explore;
+ Gesture.call(this, aTimeStamp, aPoints, aLastEvent);
+ checkProgressGesture(this);
+}
+
+Swipe.prototype = Object.create(Gesture.prototype);
+Swipe.prototype.type = 'swipe';
+Swipe.prototype._getDelay = function Swipe__getDelay(aTimeStamp) {
+ // Swipe should be completed within the GestureSettings.swipeMaxDuration from
+ // the initial pointer down event.
+ return GestureSettings.swipeMaxDuration - this.startTime + aTimeStamp;
+};
+
+/**
+ * Determine wither the gesture was Swipe or Explore.
+ * @param {Booler} aComplete A flag that indicates whether the gesture is and
+ * will be complete after the test.
+ */
+Swipe.prototype.test = function Swipe_test(aComplete) {
+ if (!aComplete) {
+ // No need to test if the gesture is not completing or can't be complete.
+ return;
+ }
+ let reject = true;
+ // If at least one point travelled for more than SWIPE_MIN_DISTANCE and it was
+ // direct enough, consider it a Swipe.
+ for (let identifier in this.points) {
+ let point = this.points[identifier];
+ let directDistance = point.directDistanceTraveled;
+ if (directDistance / Utils.dpi >= SWIPE_MIN_DISTANCE ||
+ directDistance * DIRECTNESS_COEFF >= point.totalDistanceTraveled) {
+ reject = false;
+ }
+ }
+ if (reject) {
+ this._deferred.reject(Explore);
+ }
+};
+
+/**
+ * Compile a swipe related mozAccessFuGesture event detail.
+ * @return {Object} A mozAccessFuGesture detail object.
+ */
+Swipe.prototype.compile = function Swipe_compile() {
+ let type = this.type;
+ let detail = compileDetail(type, this.points,
+ {x1: 'startX', y1: 'startY', x2: 'x', y2: 'y'});
+ let deltaX = detail.deltaX;
+ let deltaY = detail.deltaY;
+ let edge = EDGE * Utils.dpi;
+ if (Math.abs(deltaX) > Math.abs(deltaY)) {
+ // Horizontal swipe.
+ let startPoints = detail.touches.map(touch => touch.x1);
+ if (deltaX > 0) {
+ detail.type = type + 'right';
+ detail.edge = Math.min.apply(null, startPoints) <= edge;
+ } else {
+ detail.type = type + 'left';
+ detail.edge =
+ Utils.win.screen.width - Math.max.apply(null, startPoints) <= edge;
+ }
+ } else {
+ // Vertical swipe.
+ let startPoints = detail.touches.map(touch => touch.y1);
+ if (deltaY > 0) {
+ detail.type = type + 'down';
+ detail.edge = Math.min.apply(null, startPoints) <= edge;
+ } else {
+ detail.type = type + 'up';
+ detail.edge =
+ Utils.win.screen.height - Math.max.apply(null, startPoints) <= edge;
+ }
+ }
+ return detail;
+};
diff --git a/accessible/jsat/OutputGenerator.jsm b/accessible/jsat/OutputGenerator.jsm
new file mode 100644
index 000000000..36b43a569
--- /dev/null
+++ b/accessible/jsat/OutputGenerator.jsm
@@ -0,0 +1,1003 @@
+/* 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/. */
+
+/* global Components, XPCOMUtils, Utils, PrefCache, States, Roles, Logger */
+/* exported UtteranceGenerator, BrailleGenerator */
+
+'use strict';
+
+const {utils: Cu, interfaces: Ci} = Components;
+
+const INCLUDE_DESC = 0x01;
+const INCLUDE_NAME = 0x02;
+const INCLUDE_VALUE = 0x04;
+const NAME_FROM_SUBTREE_RULE = 0x10;
+const IGNORE_EXPLICIT_NAME = 0x20;
+
+const OUTPUT_DESC_FIRST = 0;
+const OUTPUT_DESC_LAST = 1;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Utils', // jshint ignore:line
+ 'resource://gre/modules/accessibility/Utils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'PrefCache', // jshint ignore:line
+ 'resource://gre/modules/accessibility/Utils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Logger', // jshint ignore:line
+ 'resource://gre/modules/accessibility/Utils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Roles', // jshint ignore:line
+ 'resource://gre/modules/accessibility/Constants.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'States', // jshint ignore:line
+ 'resource://gre/modules/accessibility/Constants.jsm');
+
+this.EXPORTED_SYMBOLS = ['UtteranceGenerator', 'BrailleGenerator']; // jshint ignore:line
+
+var OutputGenerator = {
+
+ defaultOutputOrder: OUTPUT_DESC_LAST,
+
+ /**
+ * Generates output for a PivotContext.
+ * @param {PivotContext} aContext object that generates and caches
+ * context information for a given accessible and its relationship with
+ * another accessible.
+ * @return {Object} An array of speech data. Depending on the utterance order,
+ * the data describes the context for an accessible object either
+ * starting from the accessible's ancestry or accessible's subtree.
+ */
+ genForContext: function genForContext(aContext) {
+ let output = [];
+ let self = this;
+ let addOutput = function addOutput(aAccessible) {
+ output.push.apply(output, self.genForObject(aAccessible, aContext));
+ };
+ let ignoreSubtree = function ignoreSubtree(aAccessible) {
+ let roleString = Utils.AccService.getStringRole(aAccessible.role);
+ let nameRule = self.roleRuleMap[roleString] || 0;
+ // Ignore subtree if the name is explicit and the role's name rule is the
+ // NAME_FROM_SUBTREE_RULE.
+ return (((nameRule & INCLUDE_VALUE) && aAccessible.value) ||
+ ((nameRule & NAME_FROM_SUBTREE_RULE) &&
+ (Utils.getAttributes(aAccessible)['explicit-name'] === 'true' &&
+ !(nameRule & IGNORE_EXPLICIT_NAME))));
+ };
+
+ let contextStart = this._getContextStart(aContext);
+
+ if (this.outputOrder === OUTPUT_DESC_FIRST) {
+ contextStart.forEach(addOutput);
+ addOutput(aContext.accessible);
+ for (let node of aContext.subtreeGenerator(true, ignoreSubtree)) {
+ addOutput(node);
+ }
+ } else {
+ for (let node of aContext.subtreeGenerator(false, ignoreSubtree)) {
+ addOutput(node);
+ }
+ addOutput(aContext.accessible);
+
+ // If there are any documents in new ancestry, find a first one and place
+ // it in the beginning of the utterance.
+ let doc, docIndex = contextStart.findIndex(
+ ancestor => ancestor.role === Roles.DOCUMENT);
+
+ if (docIndex > -1) {
+ doc = contextStart.splice(docIndex, 1)[0];
+ }
+
+ contextStart.reverse().forEach(addOutput);
+ if (doc) {
+ output.unshift.apply(output, self.genForObject(doc, aContext));
+ }
+ }
+
+ return output;
+ },
+
+
+ /**
+ * Generates output for an object.
+ * @param {nsIAccessible} aAccessible accessible object to generate output
+ * for.
+ * @param {PivotContext} aContext object that generates and caches
+ * context information for a given accessible and its relationship with
+ * another accessible.
+ * @return {Array} A 2 element array of speech data. The first element
+ * describes the object and its state. The second element is the object's
+ * name. Whether the object's description or it's role is included is
+ * determined by {@link roleRuleMap}.
+ */
+ genForObject: function genForObject(aAccessible, aContext) {
+ let roleString = Utils.AccService.getStringRole(aAccessible.role);
+ let func = this.objectOutputFunctions[
+ OutputGenerator._getOutputName(roleString)] ||
+ this.objectOutputFunctions.defaultFunc;
+
+ let flags = this.roleRuleMap[roleString] || 0;
+
+ if (aAccessible.childCount === 0) {
+ flags |= INCLUDE_NAME;
+ }
+
+ return func.apply(this, [aAccessible, roleString,
+ Utils.getState(aAccessible), flags, aContext]);
+ },
+
+ /**
+ * Generates output for an action performed.
+ * @param {nsIAccessible} aAccessible accessible object that the action was
+ * invoked in.
+ * @param {string} aActionName the name of the action, one of the keys in
+ * {@link gActionMap}.
+ * @return {Array} A one element array with action data.
+ */
+ genForAction: function genForAction(aObject, aActionName) {}, // jshint ignore:line
+
+ /**
+ * Generates output for an announcement.
+ * @param {string} aAnnouncement unlocalized announcement.
+ * @return {Array} An announcement speech data to be localized.
+ */
+ genForAnnouncement: function genForAnnouncement(aAnnouncement) {}, // jshint ignore:line
+
+ /**
+ * Generates output for a tab state change.
+ * @param {nsIAccessible} aAccessible accessible object of the tab's attached
+ * document.
+ * @param {string} aTabState the tab state name, see
+ * {@link Presenter.tabStateChanged}.
+ * @return {Array} The tab state utterace.
+ */
+ genForTabStateChange: function genForTabStateChange(aObject, aTabState) {}, // jshint ignore:line
+
+ /**
+ * Generates output for announcing entering and leaving editing mode.
+ * @param {aIsEditing} boolean true if we are in editing mode
+ * @return {Array} The mode utterance
+ */
+ genForEditingMode: function genForEditingMode(aIsEditing) {}, // jshint ignore:line
+
+ _getContextStart: function getContextStart(aContext) {}, // jshint ignore:line
+
+ /**
+ * Adds an accessible name and description to the output if available.
+ * @param {Array} aOutput Output array.
+ * @param {nsIAccessible} aAccessible current accessible object.
+ * @param {Number} aFlags output flags.
+ */
+ _addName: function _addName(aOutput, aAccessible, aFlags) {
+ let name;
+ if ((Utils.getAttributes(aAccessible)['explicit-name'] === 'true' &&
+ !(aFlags & IGNORE_EXPLICIT_NAME)) || (aFlags & INCLUDE_NAME)) {
+ name = aAccessible.name;
+ }
+
+ let description = aAccessible.description;
+ if (description) {
+ // Compare against the calculated name unconditionally, regardless of name rule,
+ // so we can make sure we don't speak duplicated descriptions
+ let tmpName = name || aAccessible.name;
+ if (tmpName && (description !== tmpName)) {
+ name = name || '';
+ name = this.outputOrder === OUTPUT_DESC_FIRST ?
+ description + ' - ' + name :
+ name + ' - ' + description;
+ }
+ }
+
+ if (!name || !name.trim()) {
+ return;
+ }
+ aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? 'push' : 'unshift'](name);
+ },
+
+ /**
+ * Adds a landmark role to the output if available.
+ * @param {Array} aOutput Output array.
+ * @param {nsIAccessible} aAccessible current accessible object.
+ */
+ _addLandmark: function _addLandmark(aOutput, aAccessible) {
+ let landmarkName = Utils.getLandmarkName(aAccessible);
+ if (!landmarkName) {
+ return;
+ }
+ aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? 'unshift' : 'push']({
+ string: landmarkName
+ });
+ },
+
+ /**
+ * Adds math roles to the output, for a MathML accessible.
+ * @param {Array} aOutput Output array.
+ * @param {nsIAccessible} aAccessible current accessible object.
+ * @param {String} aRoleStr aAccessible's role string.
+ */
+ _addMathRoles: function _addMathRoles(aOutput, aAccessible, aRoleStr) {
+ // First, determine the actual role to use (e.g. mathmlfraction).
+ let roleStr = aRoleStr;
+ switch(aAccessible.role) {
+ case Roles.MATHML_CELL:
+ case Roles.MATHML_ENCLOSED:
+ case Roles.MATHML_LABELED_ROW:
+ case Roles.MATHML_ROOT:
+ case Roles.MATHML_SQUARE_ROOT:
+ case Roles.MATHML_TABLE:
+ case Roles.MATHML_TABLE_ROW:
+ // Use the default role string.
+ break;
+ case Roles.MATHML_MULTISCRIPTS:
+ case Roles.MATHML_OVER:
+ case Roles.MATHML_SUB:
+ case Roles.MATHML_SUB_SUP:
+ case Roles.MATHML_SUP:
+ case Roles.MATHML_UNDER:
+ case Roles.MATHML_UNDER_OVER:
+ // For scripted accessibles, use the string 'mathmlscripted'.
+ roleStr = 'mathmlscripted';
+ break;
+ case Roles.MATHML_FRACTION:
+ // From a semantic point of view, the only important point is to
+ // distinguish between fractions that have a bar and those that do not.
+ // Per the MathML 3 spec, the latter happens iff the linethickness
+ // attribute is of the form [zero-float][optional-unit]. In that case,
+ // we use the string 'mathmlfractionwithoutbar'.
+ let linethickness = Utils.getAttributes(aAccessible).linethickness;
+ if (linethickness) {
+ let numberMatch = linethickness.match(/^(?:\d|\.)+/);
+ if (numberMatch && !parseFloat(numberMatch[0])) {
+ roleStr += 'withoutbar';
+ }
+ }
+ break;
+ default:
+ // Otherwise, do not output the actual role.
+ roleStr = null;
+ break;
+ }
+
+ // Get the math role based on the position in the parent accessible
+ // (e.g. numerator for the first child of a mathmlfraction).
+ let mathRole = Utils.getMathRole(aAccessible);
+ if (mathRole) {
+ aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? 'push' : 'unshift']
+ ({string: this._getOutputName(mathRole)});
+ }
+ if (roleStr) {
+ aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? 'push' : 'unshift']
+ ({string: this._getOutputName(roleStr)});
+ }
+ },
+
+ /**
+ * Adds MathML menclose notations to the output.
+ * @param {Array} aOutput Output array.
+ * @param {nsIAccessible} aAccessible current accessible object.
+ */
+ _addMencloseNotations: function _addMencloseNotations(aOutput, aAccessible) {
+ let notations = Utils.getAttributes(aAccessible).notation || 'longdiv';
+ aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? 'push' : 'unshift'].apply(
+ aOutput, notations.split(' ').map(notation => {
+ return { string: this._getOutputName('notation-' + notation) };
+ }));
+ },
+
+ /**
+ * Adds an entry type attribute to the description if available.
+ * @param {Array} aOutput Output array.
+ * @param {nsIAccessible} aAccessible current accessible object.
+ * @param {String} aRoleStr aAccessible's role string.
+ */
+ _addType: function _addType(aOutput, aAccessible, aRoleStr) {
+ if (aRoleStr !== 'entry') {
+ return;
+ }
+
+ let typeName = Utils.getAttributes(aAccessible)['text-input-type'];
+ // Ignore the the input type="text" case.
+ if (!typeName || typeName === 'text') {
+ return;
+ }
+ aOutput.push({string: 'textInputType_' + typeName});
+ },
+
+ _addState: function _addState(aOutput, aState, aRoleStr) {}, // jshint ignore:line
+
+ _addRole: function _addRole(aOutput, aAccessible, aRoleStr) {}, // jshint ignore:line
+
+ get outputOrder() {
+ if (!this._utteranceOrder) {
+ this._utteranceOrder = new PrefCache('accessibility.accessfu.utterance');
+ }
+ return typeof this._utteranceOrder.value === 'number' ?
+ this._utteranceOrder.value : this.defaultOutputOrder;
+ },
+
+ _getOutputName: function _getOutputName(aName) {
+ return aName.replace(/\s/g, '');
+ },
+
+ roleRuleMap: {
+ 'menubar': INCLUDE_DESC,
+ 'scrollbar': INCLUDE_DESC,
+ 'grip': INCLUDE_DESC,
+ 'alert': INCLUDE_DESC | INCLUDE_NAME,
+ 'menupopup': INCLUDE_DESC,
+ 'menuitem': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+ 'tooltip': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+ 'columnheader': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+ 'rowheader': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+ 'column': NAME_FROM_SUBTREE_RULE,
+ 'row': NAME_FROM_SUBTREE_RULE,
+ 'cell': INCLUDE_DESC | INCLUDE_NAME,
+ 'application': INCLUDE_NAME,
+ 'document': INCLUDE_NAME,
+ 'grouping': INCLUDE_DESC | INCLUDE_NAME,
+ 'toolbar': INCLUDE_DESC,
+ 'table': INCLUDE_DESC | INCLUDE_NAME,
+ 'link': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+ 'helpballoon': NAME_FROM_SUBTREE_RULE,
+ 'list': INCLUDE_DESC | INCLUDE_NAME,
+ 'listitem': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+ 'outline': INCLUDE_DESC,
+ 'outlineitem': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+ 'pagetab': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+ 'graphic': INCLUDE_DESC,
+ 'switch': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+ 'pushbutton': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+ 'checkbutton': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+ 'radiobutton': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+ 'buttondropdown': NAME_FROM_SUBTREE_RULE,
+ 'combobox': INCLUDE_DESC | INCLUDE_VALUE,
+ 'droplist': INCLUDE_DESC,
+ 'progressbar': INCLUDE_DESC | INCLUDE_VALUE,
+ 'slider': INCLUDE_DESC | INCLUDE_VALUE,
+ 'spinbutton': INCLUDE_DESC | INCLUDE_VALUE,
+ 'diagram': INCLUDE_DESC,
+ 'animation': INCLUDE_DESC,
+ 'equation': INCLUDE_DESC,
+ 'buttonmenu': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+ 'buttondropdowngrid': NAME_FROM_SUBTREE_RULE,
+ 'pagetablist': INCLUDE_DESC,
+ 'canvas': INCLUDE_DESC,
+ 'check menu item': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+ 'label': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+ 'password text': INCLUDE_DESC,
+ 'popup menu': INCLUDE_DESC,
+ 'radio menu item': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+ 'table column header': NAME_FROM_SUBTREE_RULE,
+ 'table row header': NAME_FROM_SUBTREE_RULE,
+ 'tear off menu item': NAME_FROM_SUBTREE_RULE,
+ 'toggle button': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+ 'parent menuitem': NAME_FROM_SUBTREE_RULE,
+ 'header': INCLUDE_DESC,
+ 'footer': INCLUDE_DESC,
+ 'entry': INCLUDE_DESC | INCLUDE_NAME | INCLUDE_VALUE,
+ 'caption': INCLUDE_DESC,
+ 'document frame': INCLUDE_DESC,
+ 'heading': INCLUDE_DESC,
+ 'calendar': INCLUDE_DESC | INCLUDE_NAME,
+ 'combobox option': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+ 'listbox option': INCLUDE_DESC | NAME_FROM_SUBTREE_RULE,
+ 'listbox rich option': NAME_FROM_SUBTREE_RULE,
+ 'gridcell': NAME_FROM_SUBTREE_RULE,
+ 'check rich option': NAME_FROM_SUBTREE_RULE,
+ 'term': NAME_FROM_SUBTREE_RULE,
+ 'definition': NAME_FROM_SUBTREE_RULE,
+ 'key': NAME_FROM_SUBTREE_RULE,
+ 'image map': INCLUDE_DESC,
+ 'option': INCLUDE_DESC,
+ 'listbox': INCLUDE_DESC,
+ 'definitionlist': INCLUDE_DESC | INCLUDE_NAME,
+ 'dialog': INCLUDE_DESC | INCLUDE_NAME,
+ 'chrome window': IGNORE_EXPLICIT_NAME,
+ 'app root': IGNORE_EXPLICIT_NAME,
+ 'statusbar': NAME_FROM_SUBTREE_RULE,
+ 'mathml table': INCLUDE_DESC | INCLUDE_NAME,
+ 'mathml labeled row': NAME_FROM_SUBTREE_RULE,
+ 'mathml table row': NAME_FROM_SUBTREE_RULE,
+ 'mathml cell': INCLUDE_DESC | INCLUDE_NAME,
+ 'mathml fraction': INCLUDE_DESC,
+ 'mathml square root': INCLUDE_DESC,
+ 'mathml root': INCLUDE_DESC,
+ 'mathml enclosed': INCLUDE_DESC,
+ 'mathml sub': INCLUDE_DESC,
+ 'mathml sup': INCLUDE_DESC,
+ 'mathml sub sup': INCLUDE_DESC,
+ 'mathml under': INCLUDE_DESC,
+ 'mathml over': INCLUDE_DESC,
+ 'mathml under over': INCLUDE_DESC,
+ 'mathml multiscripts': INCLUDE_DESC,
+ 'mathml identifier': INCLUDE_DESC,
+ 'mathml number': INCLUDE_DESC,
+ 'mathml operator': INCLUDE_DESC,
+ 'mathml text': INCLUDE_DESC,
+ 'mathml string literal': INCLUDE_DESC,
+ 'mathml row': INCLUDE_DESC,
+ 'mathml style': INCLUDE_DESC,
+ 'mathml error': INCLUDE_DESC },
+
+ mathmlRolesSet: new Set([
+ Roles.MATHML_MATH,
+ Roles.MATHML_IDENTIFIER,
+ Roles.MATHML_NUMBER,
+ Roles.MATHML_OPERATOR,
+ Roles.MATHML_TEXT,
+ Roles.MATHML_STRING_LITERAL,
+ Roles.MATHML_GLYPH,
+ Roles.MATHML_ROW,
+ Roles.MATHML_FRACTION,
+ Roles.MATHML_SQUARE_ROOT,
+ Roles.MATHML_ROOT,
+ Roles.MATHML_FENCED,
+ Roles.MATHML_ENCLOSED,
+ Roles.MATHML_STYLE,
+ Roles.MATHML_SUB,
+ Roles.MATHML_SUP,
+ Roles.MATHML_SUB_SUP,
+ Roles.MATHML_UNDER,
+ Roles.MATHML_OVER,
+ Roles.MATHML_UNDER_OVER,
+ Roles.MATHML_MULTISCRIPTS,
+ Roles.MATHML_TABLE,
+ Roles.LABELED_ROW,
+ Roles.MATHML_TABLE_ROW,
+ Roles.MATHML_CELL,
+ Roles.MATHML_ACTION,
+ Roles.MATHML_ERROR,
+ Roles.MATHML_STACK,
+ Roles.MATHML_LONG_DIVISION,
+ Roles.MATHML_STACK_GROUP,
+ Roles.MATHML_STACK_ROW,
+ Roles.MATHML_STACK_CARRIES,
+ Roles.MATHML_STACK_CARRY,
+ Roles.MATHML_STACK_LINE
+ ]),
+
+ objectOutputFunctions: {
+ _generateBaseOutput:
+ function _generateBaseOutput(aAccessible, aRoleStr, aState, aFlags) {
+ let output = [];
+
+ if (aFlags & INCLUDE_DESC) {
+ this._addState(output, aState, aRoleStr);
+ this._addType(output, aAccessible, aRoleStr);
+ this._addRole(output, aAccessible, aRoleStr);
+ }
+
+ if (aFlags & INCLUDE_VALUE && aAccessible.value.trim()) {
+ output[this.outputOrder === OUTPUT_DESC_FIRST ? 'push' : 'unshift'](
+ aAccessible.value);
+ }
+
+ this._addName(output, aAccessible, aFlags);
+ this._addLandmark(output, aAccessible);
+
+ return output;
+ },
+
+ label: function label(aAccessible, aRoleStr, aState, aFlags, aContext) {
+ if (aContext.isNestedControl ||
+ aContext.accessible == Utils.getEmbeddedControl(aAccessible)) {
+ // If we are on a nested control, or a nesting label,
+ // we don't need the context.
+ return [];
+ }
+
+ return this.objectOutputFunctions.defaultFunc.apply(this, arguments);
+ },
+
+ entry: function entry(aAccessible, aRoleStr, aState, aFlags) {
+ let rolestr = aState.contains(States.MULTI_LINE) ? 'textarea' : 'entry';
+ return this.objectOutputFunctions.defaultFunc.apply(
+ this, [aAccessible, rolestr, aState, aFlags]);
+ },
+
+ pagetab: function pagetab(aAccessible, aRoleStr, aState, aFlags) {
+ let itemno = {};
+ let itemof = {};
+ aAccessible.groupPosition({}, itemof, itemno);
+ let output = [];
+ this._addState(output, aState);
+ this._addRole(output, aAccessible, aRoleStr);
+ output.push({
+ string: 'objItemOfN',
+ args: [itemno.value, itemof.value]
+ });
+
+ this._addName(output, aAccessible, aFlags);
+ this._addLandmark(output, aAccessible);
+
+ return output;
+ },
+
+ table: function table(aAccessible, aRoleStr, aState, aFlags) {
+ let output = [];
+ let table;
+ try {
+ table = aAccessible.QueryInterface(Ci.nsIAccessibleTable);
+ } catch (x) {
+ Logger.logException(x);
+ return output;
+ } finally {
+ // Check if it's a layout table, and bail out if true.
+ // We don't want to speak any table information for layout tables.
+ if (table.isProbablyForLayout()) {
+ return output;
+ }
+ this._addRole(output, aAccessible, aRoleStr);
+ output.push.call(output, {
+ string: this._getOutputName('tblColumnInfo'),
+ count: table.columnCount
+ }, {
+ string: this._getOutputName('tblRowInfo'),
+ count: table.rowCount
+ });
+ this._addName(output, aAccessible, aFlags);
+ this._addLandmark(output, aAccessible);
+ return output;
+ }
+ },
+
+ gridcell: function gridcell(aAccessible, aRoleStr, aState, aFlags) {
+ let output = [];
+ this._addState(output, aState);
+ this._addName(output, aAccessible, aFlags);
+ this._addLandmark(output, aAccessible);
+ return output;
+ },
+
+ // Use the table output functions for MathML tabular elements.
+ mathmltable: function mathmltable() {
+ return this.objectOutputFunctions.table.apply(this, arguments);
+ },
+
+ mathmlcell: function mathmlcell() {
+ return this.objectOutputFunctions.cell.apply(this, arguments);
+ },
+
+ mathmlenclosed: function mathmlenclosed(aAccessible, aRoleStr, aState,
+ aFlags, aContext) {
+ let output = this.objectOutputFunctions.defaultFunc.
+ apply(this, [aAccessible, aRoleStr, aState, aFlags, aContext]);
+ this._addMencloseNotations(output, aAccessible);
+ return output;
+ }
+ }
+};
+
+/**
+ * Generates speech utterances from objects, actions and state changes.
+ * An utterance is an array of speech data.
+ *
+ * It should not be assumed that flattening an utterance array would create a
+ * gramatically correct sentence. For example, {@link genForObject} might
+ * return: ['graphic', 'Welcome to my home page'].
+ * Each string element in an utterance should be gramatically correct in itself.
+ * Another example from {@link genForObject}: ['list item 2 of 5', 'Alabama'].
+ *
+ * An utterance is ordered from the least to the most important. Speaking the
+ * last string usually makes sense, but speaking the first often won't.
+ * For example {@link genForAction} might return ['button', 'clicked'] for a
+ * clicked event. Speaking only 'clicked' makes sense. Speaking 'button' does
+ * not.
+ */
+this.UtteranceGenerator = { // jshint ignore:line
+ __proto__: OutputGenerator, // jshint ignore:line
+
+ gActionMap: {
+ jump: 'jumpAction',
+ press: 'pressAction',
+ check: 'checkAction',
+ uncheck: 'uncheckAction',
+ on: 'onAction',
+ off: 'offAction',
+ select: 'selectAction',
+ unselect: 'unselectAction',
+ open: 'openAction',
+ close: 'closeAction',
+ switch: 'switchAction',
+ click: 'clickAction',
+ collapse: 'collapseAction',
+ expand: 'expandAction',
+ activate: 'activateAction',
+ cycle: 'cycleAction'
+ },
+
+ //TODO: May become more verbose in the future.
+ genForAction: function genForAction(aObject, aActionName) {
+ return [{string: this.gActionMap[aActionName]}];
+ },
+
+ genForLiveRegion:
+ function genForLiveRegion(aContext, aIsHide, aModifiedText) {
+ let utterance = [];
+ if (aIsHide) {
+ utterance.push({string: 'hidden'});
+ }
+ return utterance.concat(aModifiedText || this.genForContext(aContext));
+ },
+
+ genForAnnouncement: function genForAnnouncement(aAnnouncement) {
+ return [{
+ string: aAnnouncement
+ }];
+ },
+
+ genForTabStateChange: function genForTabStateChange(aObject, aTabState) {
+ switch (aTabState) {
+ case 'newtab':
+ return [{string: 'tabNew'}];
+ case 'loading':
+ return [{string: 'tabLoading'}];
+ case 'loaded':
+ return [aObject.name, {string: 'tabLoaded'}];
+ case 'loadstopped':
+ return [{string: 'tabLoadStopped'}];
+ case 'reload':
+ return [{string: 'tabReload'}];
+ default:
+ return [];
+ }
+ },
+
+ genForEditingMode: function genForEditingMode(aIsEditing) {
+ return [{string: aIsEditing ? 'editingMode' : 'navigationMode'}];
+ },
+
+ objectOutputFunctions: {
+
+ __proto__: OutputGenerator.objectOutputFunctions, // jshint ignore:line
+
+ defaultFunc: function defaultFunc() {
+ return this.objectOutputFunctions._generateBaseOutput.apply(
+ this, arguments);
+ },
+
+ heading: function heading(aAccessible, aRoleStr, aState, aFlags) {
+ let level = {};
+ aAccessible.groupPosition(level, {}, {});
+ let utterance = [{string: 'headingLevel', args: [level.value]}];
+
+ this._addName(utterance, aAccessible, aFlags);
+ this._addLandmark(utterance, aAccessible);
+
+ return utterance;
+ },
+
+ listitem: function listitem(aAccessible, aRoleStr, aState, aFlags) {
+ let itemno = {};
+ let itemof = {};
+ aAccessible.groupPosition({}, itemof, itemno);
+ let utterance = [];
+ if (itemno.value == 1) {
+ // Start of list
+ utterance.push({string: 'listStart'});
+ }
+ else if (itemno.value == itemof.value) {
+ // last item
+ utterance.push({string: 'listEnd'});
+ }
+
+ this._addName(utterance, aAccessible, aFlags);
+ this._addLandmark(utterance, aAccessible);
+
+ return utterance;
+ },
+
+ list: function list(aAccessible, aRoleStr, aState, aFlags) {
+ return this._getListUtterance
+ (aAccessible, aRoleStr, aFlags, aAccessible.childCount);
+ },
+
+ definitionlist:
+ function definitionlist(aAccessible, aRoleStr, aState, aFlags) {
+ return this._getListUtterance
+ (aAccessible, aRoleStr, aFlags, aAccessible.childCount / 2);
+ },
+
+ application: function application(aAccessible, aRoleStr, aState, aFlags) {
+ // Don't utter location of applications, it gets tiring.
+ if (aAccessible.name != aAccessible.DOMNode.location) {
+ return this.objectOutputFunctions.defaultFunc.apply(this,
+ [aAccessible, aRoleStr, aState, aFlags]);
+ }
+
+ return [];
+ },
+
+ cell: function cell(aAccessible, aRoleStr, aState, aFlags, aContext) {
+ let utterance = [];
+ let cell = aContext.getCellInfo(aAccessible);
+ if (cell) {
+ let addCellChanged =
+ function addCellChanged(aUtterance, aChanged, aString, aIndex) {
+ if (aChanged) {
+ aUtterance.push({string: aString, args: [aIndex + 1]});
+ }
+ };
+ let addExtent = function addExtent(aUtterance, aExtent, aString) {
+ if (aExtent > 1) {
+ aUtterance.push({string: aString, args: [aExtent]});
+ }
+ };
+ let addHeaders = function addHeaders(aUtterance, aHeaders) {
+ if (aHeaders.length > 0) {
+ aUtterance.push.apply(aUtterance, aHeaders);
+ }
+ };
+
+ addCellChanged(utterance, cell.columnChanged, 'columnInfo',
+ cell.columnIndex);
+ addCellChanged(utterance, cell.rowChanged, 'rowInfo', cell.rowIndex);
+
+ addExtent(utterance, cell.columnExtent, 'spansColumns');
+ addExtent(utterance, cell.rowExtent, 'spansRows');
+
+ addHeaders(utterance, cell.columnHeaders);
+ addHeaders(utterance, cell.rowHeaders);
+ }
+
+ this._addName(utterance, aAccessible, aFlags);
+ this._addLandmark(utterance, aAccessible);
+
+ return utterance;
+ },
+
+ columnheader: function columnheader() {
+ return this.objectOutputFunctions.cell.apply(this, arguments);
+ },
+
+ rowheader: function rowheader() {
+ return this.objectOutputFunctions.cell.apply(this, arguments);
+ },
+
+ statictext: function statictext(aAccessible) {
+ if (Utils.isListItemDecorator(aAccessible, true)) {
+ return [];
+ }
+
+ return this.objectOutputFunctions.defaultFunc.apply(this, arguments);
+ }
+ },
+
+ _getContextStart: function _getContextStart(aContext) {
+ return aContext.newAncestry;
+ },
+
+ _addRole: function _addRole(aOutput, aAccessible, aRoleStr) {
+ if (this.mathmlRolesSet.has(aAccessible.role)) {
+ this._addMathRoles(aOutput, aAccessible, aRoleStr);
+ } else {
+ aOutput.push({string: this._getOutputName(aRoleStr)});
+ }
+ },
+
+ _addState: function _addState(aOutput, aState, aRoleStr) {
+
+ if (aState.contains(States.UNAVAILABLE)) {
+ aOutput.push({string: 'stateUnavailable'});
+ }
+
+ if (aState.contains(States.READONLY)) {
+ aOutput.push({string: 'stateReadonly'});
+ }
+
+ // Don't utter this in Jelly Bean, we let TalkBack do it for us there.
+ // This is because we expose the checked information on the node itself.
+ // XXX: this means the checked state is always appended to the end,
+ // regardless of the utterance ordering preference.
+ if ((Utils.AndroidSdkVersion < 16 || Utils.MozBuildApp === 'browser') &&
+ aState.contains(States.CHECKABLE)) {
+ let checked = aState.contains(States.CHECKED);
+ let statetr;
+ if (aRoleStr === 'switch') {
+ statetr = checked ? 'stateOn' : 'stateOff';
+ } else {
+ statetr = checked ? 'stateChecked' : 'stateNotChecked';
+ }
+ aOutput.push({string: statetr});
+ }
+
+ if (aState.contains(States.PRESSED)) {
+ aOutput.push({string: 'statePressed'});
+ }
+
+ if (aState.contains(States.EXPANDABLE)) {
+ let statetr = aState.contains(States.EXPANDED) ?
+ 'stateExpanded' : 'stateCollapsed';
+ aOutput.push({string: statetr});
+ }
+
+ if (aState.contains(States.REQUIRED)) {
+ aOutput.push({string: 'stateRequired'});
+ }
+
+ if (aState.contains(States.TRAVERSED)) {
+ aOutput.push({string: 'stateTraversed'});
+ }
+
+ if (aState.contains(States.HASPOPUP)) {
+ aOutput.push({string: 'stateHasPopup'});
+ }
+
+ if (aState.contains(States.SELECTED)) {
+ aOutput.push({string: 'stateSelected'});
+ }
+ },
+
+ _getListUtterance:
+ function _getListUtterance(aAccessible, aRoleStr, aFlags, aItemCount) {
+ let utterance = [];
+ this._addRole(utterance, aAccessible, aRoleStr);
+ utterance.push({
+ string: this._getOutputName('listItemsCount'),
+ count: aItemCount
+ });
+
+ this._addName(utterance, aAccessible, aFlags);
+ this._addLandmark(utterance, aAccessible);
+
+ return utterance;
+ }
+};
+
+this.BrailleGenerator = { // jshint ignore:line
+ __proto__: OutputGenerator, // jshint ignore:line
+
+ genForContext: function genForContext(aContext) {
+ let output = OutputGenerator.genForContext.apply(this, arguments);
+
+ let acc = aContext.accessible;
+
+ // add the static text indicating a list item; do this for both listitems or
+ // direct first children of listitems, because these are both common
+ // browsing scenarios
+ let addListitemIndicator = function addListitemIndicator(indicator = '*') {
+ output.unshift(indicator);
+ };
+
+ if (acc.indexInParent === 1 &&
+ acc.parent.role == Roles.LISTITEM &&
+ acc.previousSibling.role == Roles.STATICTEXT) {
+ if (acc.parent.parent && acc.parent.parent.DOMNode &&
+ acc.parent.parent.DOMNode.nodeName == 'UL') {
+ addListitemIndicator();
+ } else {
+ addListitemIndicator(acc.previousSibling.name.trim());
+ }
+ } else if (acc.role == Roles.LISTITEM && acc.firstChild &&
+ acc.firstChild.role == Roles.STATICTEXT) {
+ if (acc.parent.DOMNode.nodeName == 'UL') {
+ addListitemIndicator();
+ } else {
+ addListitemIndicator(acc.firstChild.name.trim());
+ }
+ }
+
+ return output;
+ },
+
+ objectOutputFunctions: {
+
+ __proto__: OutputGenerator.objectOutputFunctions, // jshint ignore:line
+
+ defaultFunc: function defaultFunc() {
+ return this.objectOutputFunctions._generateBaseOutput.apply(
+ this, arguments);
+ },
+
+ listitem: function listitem(aAccessible, aRoleStr, aState, aFlags) {
+ let braille = [];
+
+ this._addName(braille, aAccessible, aFlags);
+ this._addLandmark(braille, aAccessible);
+
+ return braille;
+ },
+
+ cell: function cell(aAccessible, aRoleStr, aState, aFlags, aContext) {
+ let braille = [];
+ let cell = aContext.getCellInfo(aAccessible);
+ if (cell) {
+ let addHeaders = function addHeaders(aBraille, aHeaders) {
+ if (aHeaders.length > 0) {
+ aBraille.push.apply(aBraille, aHeaders);
+ }
+ };
+
+ braille.push({
+ string: this._getOutputName('cellInfo'),
+ args: [cell.columnIndex + 1, cell.rowIndex + 1]
+ });
+
+ addHeaders(braille, cell.columnHeaders);
+ addHeaders(braille, cell.rowHeaders);
+ }
+
+ this._addName(braille, aAccessible, aFlags);
+ this._addLandmark(braille, aAccessible);
+ return braille;
+ },
+
+ columnheader: function columnheader() {
+ return this.objectOutputFunctions.cell.apply(this, arguments);
+ },
+
+ rowheader: function rowheader() {
+ return this.objectOutputFunctions.cell.apply(this, arguments);
+ },
+
+ statictext: function statictext(aAccessible) {
+ // Since we customize the list bullet's output, we add the static
+ // text from the first node in each listitem, so skip it here.
+ if (Utils.isListItemDecorator(aAccessible)) {
+ return [];
+ }
+
+ return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
+ },
+
+ _useStateNotRole:
+ function _useStateNotRole(aAccessible, aRoleStr, aState, aFlags) {
+ let braille = [];
+ this._addState(braille, aState, aRoleStr);
+ this._addName(braille, aAccessible, aFlags);
+ this._addLandmark(braille, aAccessible);
+
+ return braille;
+ },
+
+ switch: function braille_generator_object_output_functions_switch() {
+ return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
+ },
+
+ checkbutton: function checkbutton() {
+ return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
+ },
+
+ radiobutton: function radiobutton() {
+ return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
+ },
+
+ togglebutton: function togglebutton() {
+ return this.objectOutputFunctions._useStateNotRole.apply(this, arguments);
+ }
+ },
+
+ _getContextStart: function _getContextStart(aContext) {
+ if (aContext.accessible.parent.role == Roles.LINK) {
+ return [aContext.accessible.parent];
+ }
+
+ return [];
+ },
+
+ _getOutputName: function _getOutputName(aName) {
+ return OutputGenerator._getOutputName(aName) + 'Abbr';
+ },
+
+ _addRole: function _addRole(aBraille, aAccessible, aRoleStr) {
+ if (this.mathmlRolesSet.has(aAccessible.role)) {
+ this._addMathRoles(aBraille, aAccessible, aRoleStr);
+ } else {
+ aBraille.push({string: this._getOutputName(aRoleStr)});
+ }
+ },
+
+ _addState: function _addState(aBraille, aState, aRoleStr) {
+ if (aState.contains(States.CHECKABLE)) {
+ aBraille.push({
+ string: aState.contains(States.CHECKED) ?
+ this._getOutputName('stateChecked') :
+ this._getOutputName('stateUnchecked')
+ });
+ }
+ if (aRoleStr === 'toggle button') {
+ aBraille.push({
+ string: aState.contains(States.PRESSED) ?
+ this._getOutputName('statePressed') :
+ this._getOutputName('stateUnpressed')
+ });
+ }
+ }
+};
diff --git a/accessible/jsat/PointerAdapter.jsm b/accessible/jsat/PointerAdapter.jsm
new file mode 100644
index 000000000..ff54976b7
--- /dev/null
+++ b/accessible/jsat/PointerAdapter.jsm
@@ -0,0 +1,174 @@
+/* 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/. */
+
+/* global Components, XPCOMUtils, Utils, Logger, GestureSettings,
+ GestureTracker */
+/* exported PointerRelay, PointerAdapter */
+
+'use strict';
+
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+this.EXPORTED_SYMBOLS = ['PointerRelay', 'PointerAdapter']; // jshint ignore:line
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+
+XPCOMUtils.defineLazyModuleGetter(this, 'Utils', // jshint ignore:line
+ 'resource://gre/modules/accessibility/Utils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Logger', // jshint ignore:line
+ 'resource://gre/modules/accessibility/Utils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'GestureSettings', // jshint ignore:line
+ 'resource://gre/modules/accessibility/Gestures.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'GestureTracker', // jshint ignore:line
+ 'resource://gre/modules/accessibility/Gestures.jsm');
+
+// The virtual touch ID generated by a mouse event.
+const MOUSE_ID = 'mouse';
+// Synthesized touch ID.
+const SYNTH_ID = -1;
+
+var PointerRelay = { // jshint ignore:line
+ /**
+ * A mapping of events we should be intercepting. Entries with a value of
+ * |true| are used for compiling high-level gesture events. Entries with a
+ * value of |false| are cancelled and do not propogate to content.
+ */
+ get _eventsOfInterest() {
+ delete this._eventsOfInterest;
+
+ switch (Utils.widgetToolkit) {
+ case 'android':
+ this._eventsOfInterest = {
+ 'touchstart' : true,
+ 'touchmove' : true,
+ 'touchend' : true };
+ break;
+
+ case 'gonk':
+ this._eventsOfInterest = {
+ 'touchstart' : true,
+ 'touchmove' : true,
+ 'touchend' : true,
+ 'mousedown' : false,
+ 'mousemove' : false,
+ 'mouseup': false,
+ 'click': false };
+ break;
+
+ default:
+ // Desktop.
+ this._eventsOfInterest = {
+ 'mousemove' : true,
+ 'mousedown' : true,
+ 'mouseup': true,
+ 'click': false
+ };
+ if ('ontouchstart' in Utils.win) {
+ for (let eventType of ['touchstart', 'touchmove', 'touchend']) {
+ this._eventsOfInterest[eventType] = true;
+ }
+ }
+ break;
+ }
+
+ return this._eventsOfInterest;
+ },
+
+ _eventMap: {
+ 'touchstart' : 'pointerdown',
+ 'mousedown' : 'pointerdown',
+ 'touchmove' : 'pointermove',
+ 'mousemove' : 'pointermove',
+ 'touchend' : 'pointerup',
+ 'mouseup': 'pointerup'
+ },
+
+ start: function PointerRelay_start(aOnPointerEvent) {
+ Logger.debug('PointerRelay.start');
+ this.onPointerEvent = aOnPointerEvent;
+ for (let eventType in this._eventsOfInterest) {
+ Utils.win.addEventListener(eventType, this, true, true);
+ }
+ },
+
+ stop: function PointerRelay_stop() {
+ Logger.debug('PointerRelay.stop');
+ delete this.lastPointerMove;
+ delete this.onPointerEvent;
+ for (let eventType in this._eventsOfInterest) {
+ Utils.win.removeEventListener(eventType, this, true, true);
+ }
+ },
+
+ handleEvent: function PointerRelay_handleEvent(aEvent) {
+ // Don't bother with chrome mouse events.
+ if (Utils.MozBuildApp === 'browser' &&
+ aEvent.view.top instanceof Ci.nsIDOMChromeWindow) {
+ return;
+ }
+ if (aEvent.mozInputSource === Ci.nsIDOMMouseEvent.MOZ_SOURCE_UNKNOWN ||
+ aEvent.isSynthesized) {
+ // Ignore events that are scripted or clicks from the a11y API.
+ return;
+ }
+
+ let changedTouches = aEvent.changedTouches || [{
+ identifier: MOUSE_ID,
+ screenX: aEvent.screenX,
+ screenY: aEvent.screenY,
+ target: aEvent.target
+ }];
+
+ if (Utils.widgetToolkit === 'android' &&
+ changedTouches.length === 1 && changedTouches[0].identifier === 1) {
+ return;
+ }
+
+ if (changedTouches.length === 1 &&
+ changedTouches[0].identifier === SYNTH_ID) {
+ return;
+ }
+
+ aEvent.preventDefault();
+ aEvent.stopImmediatePropagation();
+
+ let type = aEvent.type;
+ if (!this._eventsOfInterest[type]) {
+ return;
+ }
+ let pointerType = this._eventMap[type];
+ this.onPointerEvent({
+ type: pointerType,
+ points: Array.prototype.map.call(changedTouches,
+ function mapTouch(aTouch) {
+ return {
+ identifier: aTouch.identifier,
+ x: aTouch.screenX,
+ y: aTouch.screenY
+ };
+ }
+ )
+ });
+ }
+};
+
+this.PointerAdapter = { // jshint ignore:line
+ start: function PointerAdapter_start() {
+ Logger.debug('PointerAdapter.start');
+ GestureTracker.reset();
+ PointerRelay.start(this.handleEvent);
+ },
+
+ stop: function PointerAdapter_stop() {
+ Logger.debug('PointerAdapter.stop');
+ PointerRelay.stop();
+ GestureTracker.reset();
+ },
+
+ handleEvent: function PointerAdapter_handleEvent(aDetail) {
+ let timeStamp = Date.now();
+ GestureTracker.handle(aDetail, timeStamp);
+ }
+};
diff --git a/accessible/jsat/Presentation.jsm b/accessible/jsat/Presentation.jsm
new file mode 100644
index 000000000..6912d0ea5
--- /dev/null
+++ b/accessible/jsat/Presentation.jsm
@@ -0,0 +1,769 @@
+/* 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/. */
+
+/* global Components, XPCOMUtils, Utils, Logger, BraillePresenter, Presentation,
+ UtteranceGenerator, BrailleGenerator, States, Roles, PivotContext */
+/* exported Presentation */
+
+'use strict';
+
+const {utils: Cu, interfaces: Ci} = Components;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import('resource://gre/modules/accessibility/Utils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Logger', // jshint ignore:line
+ 'resource://gre/modules/accessibility/Utils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'PivotContext', // jshint ignore:line
+ 'resource://gre/modules/accessibility/Utils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'UtteranceGenerator', // jshint ignore:line
+ 'resource://gre/modules/accessibility/OutputGenerator.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'BrailleGenerator', // jshint ignore:line
+ 'resource://gre/modules/accessibility/OutputGenerator.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Roles', // jshint ignore:line
+ 'resource://gre/modules/accessibility/Constants.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'States', // jshint ignore:line
+ 'resource://gre/modules/accessibility/Constants.jsm');
+
+this.EXPORTED_SYMBOLS = ['Presentation']; // jshint ignore:line
+
+/**
+ * The interface for all presenter classes. A presenter could be, for example,
+ * a speech output module, or a visual cursor indicator.
+ */
+function Presenter() {}
+
+Presenter.prototype = {
+ /**
+ * The type of presenter. Used for matching it with the appropriate output method.
+ */
+ type: 'Base',
+
+ /**
+ * The virtual cursor's position changed.
+ * @param {PivotContext} aContext the context object for the new pivot
+ * position.
+ * @param {int} aReason the reason for the pivot change.
+ * See nsIAccessiblePivot.
+ * @param {bool} aIsFromUserInput the pivot change was invoked by the user
+ */
+ pivotChanged: function pivotChanged(aContext, aReason, aIsFromUserInput) {}, // jshint ignore:line
+
+ /**
+ * An object's action has been invoked.
+ * @param {nsIAccessible} aObject the object that has been invoked.
+ * @param {string} aActionName the name of the action.
+ */
+ actionInvoked: function actionInvoked(aObject, aActionName) {}, // jshint ignore:line
+
+ /**
+ * Text has changed, either by the user or by the system. TODO.
+ */
+ textChanged: function textChanged(aAccessible, aIsInserted, aStartOffset, // jshint ignore:line
+ aLength, aText, aModifiedText) {}, // jshint ignore:line
+
+ /**
+ * Text selection has changed. TODO.
+ */
+ textSelectionChanged: function textSelectionChanged(
+ aText, aStart, aEnd, aOldStart, aOldEnd, aIsFromUserInput) {}, // jshint ignore:line
+
+ /**
+ * Selection has changed. TODO.
+ * @param {nsIAccessible} aObject the object that has been selected.
+ */
+ selectionChanged: function selectionChanged(aObject) {}, // jshint ignore:line
+
+ /**
+ * Name has changed.
+ * @param {nsIAccessible} aAccessible the object whose value has changed.
+ */
+ nameChanged: function nameChanged(aAccessible) {}, // jshint ignore: line
+
+ /**
+ * Value has changed.
+ * @param {nsIAccessible} aAccessible the object whose value has changed.
+ */
+ valueChanged: function valueChanged(aAccessible) {}, // jshint ignore:line
+
+ /**
+ * The tab, or the tab's document state has changed.
+ * @param {nsIAccessible} aDocObj the tab document accessible that has had its
+ * state changed, or null if the tab has no associated document yet.
+ * @param {string} aPageState the state name for the tab, valid states are:
+ * 'newtab', 'loading', 'newdoc', 'loaded', 'stopped', and 'reload'.
+ */
+ tabStateChanged: function tabStateChanged(aDocObj, aPageState) {}, // jshint ignore:line
+
+ /**
+ * The current tab has changed.
+ * @param {PivotContext} aDocContext context object for tab's
+ * document.
+ * @param {PivotContext} aVCContext context object for tab's current
+ * virtual cursor position.
+ */
+ tabSelected: function tabSelected(aDocContext, aVCContext) {}, // jshint ignore:line
+
+ /**
+ * The viewport has changed, either a scroll, pan, zoom, or
+ * landscape/portrait toggle.
+ * @param {Window} aWindow window of viewport that changed.
+ * @param {PivotContext} aCurrentContext context of last pivot change.
+ */
+ viewportChanged: function viewportChanged(aWindow, aCurrentContext) {}, // jshint ignore:line
+
+ /**
+ * We have entered or left text editing mode.
+ */
+ editingModeChanged: function editingModeChanged(aIsEditing) {}, // jshint ignore:line
+
+ /**
+ * Announce something. Typically an app state change.
+ */
+ announce: function announce(aAnnouncement) {}, // jshint ignore:line
+
+
+ /**
+ * User tried to move cursor forward or backward with no success.
+ * @param {string} aMoveMethod move method that was used (eg. 'moveNext').
+ */
+ noMove: function noMove(aMoveMethod) {},
+
+ /**
+ * Announce a live region.
+ * @param {PivotContext} aContext context object for an accessible.
+ * @param {boolean} aIsPolite A politeness level for a live region.
+ * @param {boolean} aIsHide An indicator of hide/remove event.
+ * @param {string} aModifiedText Optional modified text.
+ */
+ liveRegion: function liveRegionShown(aContext, aIsPolite, aIsHide, // jshint ignore:line
+ aModifiedText) {} // jshint ignore:line
+};
+
+/**
+ * Visual presenter. Draws a box around the virtual cursor's position.
+ */
+function VisualPresenter() {}
+
+VisualPresenter.prototype = Object.create(Presenter.prototype);
+
+VisualPresenter.prototype.type = 'Visual';
+
+/**
+ * The padding in pixels between the object and the highlight border.
+ */
+VisualPresenter.prototype.BORDER_PADDING = 2;
+
+VisualPresenter.prototype.viewportChanged =
+ function VisualPresenter_viewportChanged(aWindow, aCurrentContext) {
+ if (!aCurrentContext) {
+ return null;
+ }
+
+ let currentAcc = aCurrentContext.accessibleForBounds;
+ let start = aCurrentContext.startOffset;
+ let end = aCurrentContext.endOffset;
+ if (Utils.isAliveAndVisible(currentAcc)) {
+ let bounds = (start === -1 && end === -1) ? Utils.getBounds(currentAcc) :
+ Utils.getTextBounds(currentAcc, start, end);
+
+ return {
+ type: this.type,
+ details: {
+ eventType: 'viewport-change',
+ bounds: bounds,
+ padding: this.BORDER_PADDING
+ }
+ };
+ }
+
+ return null;
+ };
+
+VisualPresenter.prototype.pivotChanged =
+ function VisualPresenter_pivotChanged(aContext) {
+ if (!aContext.accessible) {
+ // XXX: Don't hide because another vc may be using the highlight.
+ return null;
+ }
+
+ try {
+ aContext.accessibleForBounds.scrollTo(
+ Ci.nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE);
+
+ let bounds = (aContext.startOffset === -1 && aContext.endOffset === -1) ?
+ aContext.bounds : Utils.getTextBounds(aContext.accessibleForBounds,
+ aContext.startOffset,
+ aContext.endOffset);
+
+ return {
+ type: this.type,
+ details: {
+ eventType: 'vc-change',
+ bounds: bounds,
+ padding: this.BORDER_PADDING
+ }
+ };
+ } catch (e) {
+ Logger.logException(e, 'Failed to get bounds');
+ return null;
+ }
+ };
+
+VisualPresenter.prototype.tabSelected =
+ function VisualPresenter_tabSelected(aDocContext, aVCContext) {
+ return this.pivotChanged(aVCContext, Ci.nsIAccessiblePivot.REASON_NONE);
+ };
+
+VisualPresenter.prototype.tabStateChanged =
+ function VisualPresenter_tabStateChanged(aDocObj, aPageState) {
+ if (aPageState == 'newdoc') {
+ return {type: this.type, details: {eventType: 'tabstate-change'}};
+ }
+
+ return null;
+ };
+
+/**
+ * Android presenter. Fires Android a11y events.
+ */
+function AndroidPresenter() {}
+
+AndroidPresenter.prototype = Object.create(Presenter.prototype);
+
+AndroidPresenter.prototype.type = 'Android';
+
+// Android AccessibilityEvent type constants.
+AndroidPresenter.prototype.ANDROID_VIEW_CLICKED = 0x01;
+AndroidPresenter.prototype.ANDROID_VIEW_LONG_CLICKED = 0x02;
+AndroidPresenter.prototype.ANDROID_VIEW_SELECTED = 0x04;
+AndroidPresenter.prototype.ANDROID_VIEW_FOCUSED = 0x08;
+AndroidPresenter.prototype.ANDROID_VIEW_TEXT_CHANGED = 0x10;
+AndroidPresenter.prototype.ANDROID_WINDOW_STATE_CHANGED = 0x20;
+AndroidPresenter.prototype.ANDROID_VIEW_HOVER_ENTER = 0x80;
+AndroidPresenter.prototype.ANDROID_VIEW_HOVER_EXIT = 0x100;
+AndroidPresenter.prototype.ANDROID_VIEW_SCROLLED = 0x1000;
+AndroidPresenter.prototype.ANDROID_VIEW_TEXT_SELECTION_CHANGED = 0x2000;
+AndroidPresenter.prototype.ANDROID_ANNOUNCEMENT = 0x4000;
+AndroidPresenter.prototype.ANDROID_VIEW_ACCESSIBILITY_FOCUSED = 0x8000;
+AndroidPresenter.prototype.ANDROID_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY =
+ 0x20000;
+
+AndroidPresenter.prototype.pivotChanged =
+ function AndroidPresenter_pivotChanged(aContext, aReason) {
+ if (!aContext.accessible) {
+ return null;
+ }
+
+ let androidEvents = [];
+
+ let isExploreByTouch = (aReason == Ci.nsIAccessiblePivot.REASON_POINT &&
+ Utils.AndroidSdkVersion >= 14);
+ let focusEventType = (Utils.AndroidSdkVersion >= 16) ?
+ this.ANDROID_VIEW_ACCESSIBILITY_FOCUSED :
+ this.ANDROID_VIEW_FOCUSED;
+
+ if (isExploreByTouch) {
+ // This isn't really used by TalkBack so this is a half-hearted attempt
+ // for now.
+ androidEvents.push({eventType: this.ANDROID_VIEW_HOVER_EXIT, text: []});
+ }
+
+ let brailleOutput = {};
+ if (Utils.AndroidSdkVersion >= 16) {
+ if (!this._braillePresenter) {
+ this._braillePresenter = new BraillePresenter();
+ }
+ brailleOutput = this._braillePresenter.pivotChanged(aContext, aReason).
+ details;
+ }
+
+ if (aReason === Ci.nsIAccessiblePivot.REASON_TEXT) {
+ if (Utils.AndroidSdkVersion >= 16) {
+ let adjustedText = aContext.textAndAdjustedOffsets;
+
+ androidEvents.push({
+ eventType: this.ANDROID_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
+ text: [adjustedText.text],
+ fromIndex: adjustedText.startOffset,
+ toIndex: adjustedText.endOffset
+ });
+ }
+ } else {
+ let state = Utils.getState(aContext.accessible);
+ androidEvents.push({eventType: (isExploreByTouch) ?
+ this.ANDROID_VIEW_HOVER_ENTER : focusEventType,
+ text: Utils.localize(UtteranceGenerator.genForContext(
+ aContext)),
+ bounds: aContext.bounds,
+ clickable: aContext.accessible.actionCount > 0,
+ checkable: state.contains(States.CHECKABLE),
+ checked: state.contains(States.CHECKED),
+ brailleOutput: brailleOutput});
+ }
+
+
+ return {
+ type: this.type,
+ details: androidEvents
+ };
+ };
+
+AndroidPresenter.prototype.actionInvoked =
+ function AndroidPresenter_actionInvoked(aObject, aActionName) {
+ let state = Utils.getState(aObject);
+
+ // Checkable objects use TalkBack's text derived from the event state,
+ // so we don't populate the text here.
+ let text = '';
+ if (!state.contains(States.CHECKABLE)) {
+ text = Utils.localize(UtteranceGenerator.genForAction(aObject,
+ aActionName));
+ }
+
+ return {
+ type: this.type,
+ details: [{
+ eventType: this.ANDROID_VIEW_CLICKED,
+ text: text,
+ checked: state.contains(States.CHECKED)
+ }]
+ };
+ };
+
+AndroidPresenter.prototype.tabSelected =
+ function AndroidPresenter_tabSelected(aDocContext, aVCContext) {
+ // Send a pivot change message with the full context utterance for this doc.
+ return this.pivotChanged(aVCContext, Ci.nsIAccessiblePivot.REASON_NONE);
+ };
+
+AndroidPresenter.prototype.tabStateChanged =
+ function AndroidPresenter_tabStateChanged(aDocObj, aPageState) {
+ return this.announce(
+ UtteranceGenerator.genForTabStateChange(aDocObj, aPageState));
+ };
+
+AndroidPresenter.prototype.textChanged = function AndroidPresenter_textChanged(
+ aAccessible, aIsInserted, aStart, aLength, aText, aModifiedText) {
+ let eventDetails = {
+ eventType: this.ANDROID_VIEW_TEXT_CHANGED,
+ text: [aText],
+ fromIndex: aStart,
+ removedCount: 0,
+ addedCount: 0
+ };
+
+ if (aIsInserted) {
+ eventDetails.addedCount = aLength;
+ eventDetails.beforeText =
+ aText.substring(0, aStart) + aText.substring(aStart + aLength);
+ } else {
+ eventDetails.removedCount = aLength;
+ eventDetails.beforeText =
+ aText.substring(0, aStart) + aModifiedText + aText.substring(aStart);
+ }
+
+ return {type: this.type, details: [eventDetails]};
+ };
+
+AndroidPresenter.prototype.textSelectionChanged =
+ function AndroidPresenter_textSelectionChanged(aText, aStart, aEnd, aOldStart,
+ aOldEnd, aIsFromUserInput) {
+ let androidEvents = [];
+
+ if (Utils.AndroidSdkVersion >= 14 && !aIsFromUserInput) {
+ if (!this._braillePresenter) {
+ this._braillePresenter = new BraillePresenter();
+ }
+ let brailleOutput = this._braillePresenter.textSelectionChanged(
+ aText, aStart, aEnd, aOldStart, aOldEnd, aIsFromUserInput).details;
+
+ androidEvents.push({
+ eventType: this.ANDROID_VIEW_TEXT_SELECTION_CHANGED,
+ text: [aText],
+ fromIndex: aStart,
+ toIndex: aEnd,
+ itemCount: aText.length,
+ brailleOutput: brailleOutput
+ });
+ }
+
+ if (Utils.AndroidSdkVersion >= 16 && aIsFromUserInput) {
+ let [from, to] = aOldStart < aStart ?
+ [aOldStart, aStart] : [aStart, aOldStart];
+ androidEvents.push({
+ eventType: this.ANDROID_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
+ text: [aText],
+ fromIndex: from,
+ toIndex: to
+ });
+ }
+
+ return {
+ type: this.type,
+ details: androidEvents
+ };
+ };
+
+AndroidPresenter.prototype.viewportChanged =
+ function AndroidPresenter_viewportChanged(aWindow, aCurrentContext) {
+ if (Utils.AndroidSdkVersion < 14) {
+ return null;
+ }
+
+ let events = [{
+ eventType: this.ANDROID_VIEW_SCROLLED,
+ text: [],
+ scrollX: aWindow.scrollX,
+ scrollY: aWindow.scrollY,
+ maxScrollX: aWindow.scrollMaxX,
+ maxScrollY: aWindow.scrollMaxY
+ }];
+
+ if (Utils.AndroidSdkVersion >= 16 && aCurrentContext) {
+ let currentAcc = aCurrentContext.accessibleForBounds;
+ if (Utils.isAliveAndVisible(currentAcc)) {
+ events.push({
+ eventType: this.ANDROID_VIEW_ACCESSIBILITY_FOCUSED,
+ bounds: Utils.getBounds(currentAcc)
+ });
+ }
+ }
+
+ return {
+ type: this.type,
+ details: events
+ };
+ };
+
+AndroidPresenter.prototype.editingModeChanged =
+ function AndroidPresenter_editingModeChanged(aIsEditing) {
+ return this.announce(UtteranceGenerator.genForEditingMode(aIsEditing));
+ };
+
+AndroidPresenter.prototype.announce =
+ function AndroidPresenter_announce(aAnnouncement) {
+ let localizedAnnouncement = Utils.localize(aAnnouncement).join(' ');
+ return {
+ type: this.type,
+ details: [{
+ eventType: (Utils.AndroidSdkVersion >= 16) ?
+ this.ANDROID_ANNOUNCEMENT : this.ANDROID_VIEW_TEXT_CHANGED,
+ text: [localizedAnnouncement],
+ addedCount: localizedAnnouncement.length,
+ removedCount: 0,
+ fromIndex: 0
+ }]
+ };
+ };
+
+AndroidPresenter.prototype.liveRegion =
+ function AndroidPresenter_liveRegion(aContext, aIsPolite,
+ aIsHide, aModifiedText) {
+ return this.announce(
+ UtteranceGenerator.genForLiveRegion(aContext, aIsHide, aModifiedText));
+ };
+
+AndroidPresenter.prototype.noMove =
+ function AndroidPresenter_noMove(aMoveMethod) {
+ return {
+ type: this.type,
+ details: [
+ { eventType: this.ANDROID_VIEW_ACCESSIBILITY_FOCUSED,
+ exitView: aMoveMethod,
+ text: ['']
+ }]
+ };
+ };
+
+/**
+ * A B2G presenter for Gaia.
+ */
+function B2GPresenter() {}
+
+B2GPresenter.prototype = Object.create(Presenter.prototype);
+
+B2GPresenter.prototype.type = 'B2G';
+
+B2GPresenter.prototype.keyboardEchoSetting =
+ new PrefCache('accessibility.accessfu.keyboard_echo');
+B2GPresenter.prototype.NO_ECHO = 0;
+B2GPresenter.prototype.CHARACTER_ECHO = 1;
+B2GPresenter.prototype.WORD_ECHO = 2;
+B2GPresenter.prototype.CHARACTER_AND_WORD_ECHO = 3;
+
+/**
+ * A pattern used for haptic feedback.
+ * @type {Array}
+ */
+B2GPresenter.prototype.PIVOT_CHANGE_HAPTIC_PATTERN = [40];
+
+/**
+ * Pivot move reasons.
+ * @type {Array}
+ */
+B2GPresenter.prototype.pivotChangedReasons = ['none', 'next', 'prev', 'first',
+ 'last', 'text', 'point'];
+
+B2GPresenter.prototype.pivotChanged =
+ function B2GPresenter_pivotChanged(aContext, aReason, aIsUserInput) {
+ if (!aContext.accessible) {
+ return null;
+ }
+
+ return {
+ type: this.type,
+ details: {
+ eventType: 'vc-change',
+ data: UtteranceGenerator.genForContext(aContext),
+ options: {
+ pattern: this.PIVOT_CHANGE_HAPTIC_PATTERN,
+ isKey: Utils.isActivatableOnFingerUp(aContext.accessible),
+ reason: this.pivotChangedReasons[aReason],
+ isUserInput: aIsUserInput,
+ hints: aContext.interactionHints
+ }
+ }
+ };
+ };
+
+B2GPresenter.prototype.nameChanged =
+ function B2GPresenter_nameChanged(aAccessible, aIsPolite = true) {
+ return {
+ type: this.type,
+ details: {
+ eventType: 'name-change',
+ data: aAccessible.name,
+ options: {enqueue: aIsPolite}
+ }
+ };
+ };
+
+B2GPresenter.prototype.valueChanged =
+ function B2GPresenter_valueChanged(aAccessible, aIsPolite = true) {
+
+ // the editable value changes are handled in the text changed presenter
+ if (Utils.getState(aAccessible).contains(States.EDITABLE)) {
+ return null;
+ }
+
+ return {
+ type: this.type,
+ details: {
+ eventType: 'value-change',
+ data: aAccessible.value,
+ options: {enqueue: aIsPolite}
+ }
+ };
+ };
+
+B2GPresenter.prototype.textChanged = function B2GPresenter_textChanged(
+ aAccessible, aIsInserted, aStart, aLength, aText, aModifiedText) {
+ let echoSetting = this.keyboardEchoSetting.value;
+ let text = '';
+
+ if (echoSetting == this.CHARACTER_ECHO ||
+ echoSetting == this.CHARACTER_AND_WORD_ECHO) {
+ text = aModifiedText;
+ }
+
+ // add word if word boundary is added
+ if ((echoSetting == this.WORD_ECHO ||
+ echoSetting == this.CHARACTER_AND_WORD_ECHO) &&
+ aIsInserted && aLength === 1) {
+ let accText = aAccessible.QueryInterface(Ci.nsIAccessibleText);
+ let startBefore = {}, endBefore = {};
+ let startAfter = {}, endAfter = {};
+ accText.getTextBeforeOffset(aStart,
+ Ci.nsIAccessibleText.BOUNDARY_WORD_END, startBefore, endBefore);
+ let maybeWord = accText.getTextBeforeOffset(aStart + 1,
+ Ci.nsIAccessibleText.BOUNDARY_WORD_END, startAfter, endAfter);
+ if (endBefore.value !== endAfter.value) {
+ text += maybeWord;
+ }
+ }
+
+ return {
+ type: this.type,
+ details: {
+ eventType: 'text-change',
+ data: text
+ }
+ };
+
+ };
+
+B2GPresenter.prototype.actionInvoked =
+ function B2GPresenter_actionInvoked(aObject, aActionName) {
+ return {
+ type: this.type,
+ details: {
+ eventType: 'action',
+ data: UtteranceGenerator.genForAction(aObject, aActionName)
+ }
+ };
+ };
+
+B2GPresenter.prototype.liveRegion = function B2GPresenter_liveRegion(aContext,
+ aIsPolite, aIsHide, aModifiedText) {
+ return {
+ type: this.type,
+ details: {
+ eventType: 'liveregion-change',
+ data: UtteranceGenerator.genForLiveRegion(aContext, aIsHide,
+ aModifiedText),
+ options: {enqueue: aIsPolite}
+ }
+ };
+ };
+
+B2GPresenter.prototype.announce =
+ function B2GPresenter_announce(aAnnouncement) {
+ return {
+ type: this.type,
+ details: {
+ eventType: 'announcement',
+ data: aAnnouncement
+ }
+ };
+ };
+
+B2GPresenter.prototype.noMove =
+ function B2GPresenter_noMove(aMoveMethod) {
+ return {
+ type: this.type,
+ details: {
+ eventType: 'no-move',
+ data: aMoveMethod
+ }
+ };
+ };
+
+/**
+ * A braille presenter
+ */
+function BraillePresenter() {}
+
+BraillePresenter.prototype = Object.create(Presenter.prototype);
+
+BraillePresenter.prototype.type = 'Braille';
+
+BraillePresenter.prototype.pivotChanged =
+ function BraillePresenter_pivotChanged(aContext) {
+ if (!aContext.accessible) {
+ return null;
+ }
+
+ return {
+ type: this.type,
+ details: {
+ output: Utils.localize(BrailleGenerator.genForContext(aContext)).join(
+ ' '),
+ selectionStart: 0,
+ selectionEnd: 0
+ }
+ };
+ };
+
+BraillePresenter.prototype.textSelectionChanged =
+ function BraillePresenter_textSelectionChanged(aText, aStart, aEnd) {
+ return {
+ type: this.type,
+ details: {
+ selectionStart: aStart,
+ selectionEnd: aEnd
+ }
+ };
+ };
+
+this.Presentation = { // jshint ignore:line
+ get presenters() {
+ delete this.presenters;
+ let presenterMap = {
+ 'mobile/android': [VisualPresenter, AndroidPresenter],
+ 'b2g': [VisualPresenter, B2GPresenter],
+ 'browser': [VisualPresenter, B2GPresenter, AndroidPresenter]
+ };
+ this.presenters = presenterMap[Utils.MozBuildApp].map(P => new P());
+ return this.presenters;
+ },
+
+ get displayedAccessibles() {
+ delete this.displayedAccessibles;
+ this.displayedAccessibles = new WeakMap();
+ return this.displayedAccessibles;
+ },
+
+ pivotChanged: function Presentation_pivotChanged(
+ aPosition, aOldPosition, aReason, aStartOffset, aEndOffset, aIsUserInput) {
+ let context = new PivotContext(
+ aPosition, aOldPosition, aStartOffset, aEndOffset);
+ if (context.accessible) {
+ this.displayedAccessibles.set(context.accessible.document.window, context);
+ }
+
+ return this.presenters.map(p => p.pivotChanged(context, aReason, aIsUserInput));
+ },
+
+ actionInvoked: function Presentation_actionInvoked(aObject, aActionName) {
+ return this.presenters.map(p => p.actionInvoked(aObject, aActionName));
+ },
+
+ textChanged: function Presentation_textChanged(aAccessible, aIsInserted,
+ aStartOffset, aLength, aText,
+ aModifiedText) {
+ return this.presenters.map(p => p.textChanged(aAccessible, aIsInserted,
+ aStartOffset, aLength,
+ aText, aModifiedText));
+ },
+
+ textSelectionChanged: function textSelectionChanged(aText, aStart, aEnd,
+ aOldStart, aOldEnd,
+ aIsFromUserInput) {
+ return this.presenters.map(p => p.textSelectionChanged(aText, aStart, aEnd,
+ aOldStart, aOldEnd,
+ aIsFromUserInput));
+ },
+
+ nameChanged: function nameChanged(aAccessible) {
+ return this.presenters.map(p => p.nameChanged(aAccessible));
+ },
+
+ valueChanged: function valueChanged(aAccessible) {
+ return this.presenters.map(p => p.valueChanged(aAccessible));
+ },
+
+ tabStateChanged: function Presentation_tabStateChanged(aDocObj, aPageState) {
+ return this.presenters.map(p => p.tabStateChanged(aDocObj, aPageState));
+ },
+
+ viewportChanged: function Presentation_viewportChanged(aWindow) {
+ let context = this.displayedAccessibles.get(aWindow);
+ return this.presenters.map(p => p.viewportChanged(aWindow, context));
+ },
+
+ editingModeChanged: function Presentation_editingModeChanged(aIsEditing) {
+ return this.presenters.map(p => p.editingModeChanged(aIsEditing));
+ },
+
+ announce: function Presentation_announce(aAnnouncement) {
+ // XXX: Typically each presenter uses the UtteranceGenerator,
+ // but there really isn't a point here.
+ return this.presenters.map(p => p.announce(UtteranceGenerator.genForAnnouncement(aAnnouncement)));
+ },
+
+ noMove: function Presentation_noMove(aMoveMethod) {
+ return this.presenters.map(p => p.noMove(aMoveMethod));
+ },
+
+ liveRegion: function Presentation_liveRegion(aAccessible, aIsPolite, aIsHide,
+ aModifiedText) {
+ let context;
+ if (!aModifiedText) {
+ context = new PivotContext(aAccessible, null, -1, -1, true,
+ aIsHide ? true : false);
+ }
+ return this.presenters.map(p => p.liveRegion(context, aIsPolite, aIsHide,
+ aModifiedText));
+ }
+};
diff --git a/accessible/jsat/Traversal.jsm b/accessible/jsat/Traversal.jsm
new file mode 100644
index 000000000..5b3bbdf89
--- /dev/null
+++ b/accessible/jsat/Traversal.jsm
@@ -0,0 +1,419 @@
+/* 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/. */
+
+/* global PrefCache, Roles, Prefilters, States, Filters, Utils,
+ TraversalRules, Components, XPCOMUtils */
+/* exported TraversalRules, TraversalHelper */
+
+'use strict';
+
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+this.EXPORTED_SYMBOLS = ['TraversalRules', 'TraversalHelper']; // jshint ignore:line
+
+Cu.import('resource://gre/modules/accessibility/Utils.jsm');
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Roles', // jshint ignore:line
+ 'resource://gre/modules/accessibility/Constants.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Filters', // jshint ignore:line
+ 'resource://gre/modules/accessibility/Constants.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'States', // jshint ignore:line
+ 'resource://gre/modules/accessibility/Constants.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Prefilters', // jshint ignore:line
+ 'resource://gre/modules/accessibility/Constants.jsm');
+
+var gSkipEmptyImages = new PrefCache('accessibility.accessfu.skip_empty_images');
+
+function BaseTraversalRule(aRoles, aMatchFunc, aPreFilter, aContainerRule) {
+ this._explicitMatchRoles = new Set(aRoles);
+ this._matchRoles = aRoles;
+ if (aRoles.length) {
+ if (aRoles.indexOf(Roles.LABEL) < 0) {
+ this._matchRoles.push(Roles.LABEL);
+ }
+ if (aRoles.indexOf(Roles.INTERNAL_FRAME) < 0) {
+ // Used for traversing in to child OOP frames.
+ this._matchRoles.push(Roles.INTERNAL_FRAME);
+ }
+ }
+ this._matchFunc = aMatchFunc || function() { return Filters.MATCH; };
+ this.preFilter = aPreFilter || gSimplePreFilter;
+ this.containerRule = aContainerRule;
+}
+
+BaseTraversalRule.prototype = {
+ getMatchRoles: function BaseTraversalRule_getmatchRoles(aRoles) {
+ aRoles.value = this._matchRoles;
+ return aRoles.value.length;
+ },
+
+ match: function BaseTraversalRule_match(aAccessible)
+ {
+ let role = aAccessible.role;
+ if (role == Roles.INTERNAL_FRAME) {
+ return (Utils.getMessageManager(aAccessible.DOMNode)) ?
+ Filters.MATCH | Filters.IGNORE_SUBTREE : Filters.IGNORE;
+ }
+
+ let matchResult =
+ (this._explicitMatchRoles.has(role) || !this._explicitMatchRoles.size) ?
+ this._matchFunc(aAccessible) : Filters.IGNORE;
+
+ // If we are on a label that nests a checkbox/radio we should land on it.
+ // It is a bigger touch target, and it reduces clutter.
+ if (role == Roles.LABEL && !(matchResult & Filters.IGNORE_SUBTREE)) {
+ let control = Utils.getEmbeddedControl(aAccessible);
+ if (control && this._explicitMatchRoles.has(control.role)) {
+ matchResult = this._matchFunc(control) | Filters.IGNORE_SUBTREE;
+ }
+ }
+
+ return matchResult;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAccessibleTraversalRule])
+};
+
+var gSimpleTraversalRoles =
+ [Roles.MENUITEM,
+ Roles.LINK,
+ Roles.PAGETAB,
+ Roles.GRAPHIC,
+ Roles.STATICTEXT,
+ Roles.TEXT_LEAF,
+ Roles.PUSHBUTTON,
+ Roles.CHECKBUTTON,
+ Roles.RADIOBUTTON,
+ Roles.COMBOBOX,
+ Roles.PROGRESSBAR,
+ Roles.BUTTONDROPDOWN,
+ Roles.BUTTONMENU,
+ Roles.CHECK_MENU_ITEM,
+ Roles.PASSWORD_TEXT,
+ Roles.RADIO_MENU_ITEM,
+ Roles.TOGGLE_BUTTON,
+ Roles.ENTRY,
+ Roles.KEY,
+ Roles.HEADER,
+ Roles.HEADING,
+ Roles.SLIDER,
+ Roles.SPINBUTTON,
+ Roles.OPTION,
+ Roles.LISTITEM,
+ Roles.GRID_CELL,
+ Roles.COLUMNHEADER,
+ Roles.ROWHEADER,
+ Roles.STATUSBAR,
+ Roles.SWITCH,
+ Roles.MATHML_MATH];
+
+var gSimpleMatchFunc = function gSimpleMatchFunc(aAccessible) {
+ // An object is simple, if it either has a single child lineage,
+ // or has a flat subtree.
+ function isSingleLineage(acc) {
+ for (let child = acc; child; child = child.firstChild) {
+ if (Utils.visibleChildCount(child) > 1) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function isFlatSubtree(acc) {
+ for (let child = acc.firstChild; child; child = child.nextSibling) {
+ // text leafs inherit the actionCount of any ancestor that has a click
+ // listener.
+ if ([Roles.TEXT_LEAF, Roles.STATICTEXT].indexOf(child.role) >= 0) {
+ continue;
+ }
+ if (Utils.visibleChildCount(child) > 0 || child.actionCount > 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ switch (aAccessible.role) {
+ case Roles.COMBOBOX:
+ // We don't want to ignore the subtree because this is often
+ // where the list box hangs out.
+ return Filters.MATCH;
+ case Roles.TEXT_LEAF:
+ {
+ // Nameless text leaves are boring, skip them.
+ let name = aAccessible.name;
+ return (name && name.trim()) ? Filters.MATCH : Filters.IGNORE;
+ }
+ case Roles.STATICTEXT:
+ // Ignore prefix static text in list items. They are typically bullets or numbers.
+ return Utils.isListItemDecorator(aAccessible) ?
+ Filters.IGNORE : Filters.MATCH;
+ case Roles.GRAPHIC:
+ return TraversalRules._shouldSkipImage(aAccessible);
+ case Roles.HEADER:
+ case Roles.HEADING:
+ case Roles.COLUMNHEADER:
+ case Roles.ROWHEADER:
+ case Roles.STATUSBAR:
+ if ((aAccessible.childCount > 0 || aAccessible.name) &&
+ (isSingleLineage(aAccessible) || isFlatSubtree(aAccessible))) {
+ return Filters.MATCH | Filters.IGNORE_SUBTREE;
+ }
+ return Filters.IGNORE;
+ case Roles.GRID_CELL:
+ return isSingleLineage(aAccessible) || isFlatSubtree(aAccessible) ?
+ Filters.MATCH | Filters.IGNORE_SUBTREE : Filters.IGNORE;
+ case Roles.LISTITEM:
+ {
+ let item = aAccessible.childCount === 2 &&
+ aAccessible.firstChild.role === Roles.STATICTEXT ?
+ aAccessible.lastChild : aAccessible;
+ return isSingleLineage(item) || isFlatSubtree(item) ?
+ Filters.MATCH | Filters.IGNORE_SUBTREE : Filters.IGNORE;
+ }
+ default:
+ // Ignore the subtree, if there is one. So that we don't land on
+ // the same content that was already presented by its parent.
+ return Filters.MATCH |
+ Filters.IGNORE_SUBTREE;
+ }
+};
+
+var gSimplePreFilter = Prefilters.DEFUNCT |
+ Prefilters.INVISIBLE |
+ Prefilters.ARIA_HIDDEN |
+ Prefilters.TRANSPARENT;
+
+this.TraversalRules = { // jshint ignore:line
+ Simple: new BaseTraversalRule(gSimpleTraversalRoles, gSimpleMatchFunc),
+
+ SimpleOnScreen: new BaseTraversalRule(
+ gSimpleTraversalRoles, gSimpleMatchFunc,
+ Prefilters.DEFUNCT | Prefilters.INVISIBLE | Prefilters.ARIA_HIDDEN |
+ Prefilters.TRANSPARENT | Prefilters.OFFSCREEN),
+
+ Anchor: new BaseTraversalRule(
+ [Roles.LINK],
+ function Anchor_match(aAccessible)
+ {
+ // We want to ignore links, only focus named anchors.
+ if (Utils.getState(aAccessible).contains(States.LINKED)) {
+ return Filters.IGNORE;
+ } else {
+ return Filters.MATCH;
+ }
+ }),
+
+ Button: new BaseTraversalRule(
+ [Roles.PUSHBUTTON,
+ Roles.SPINBUTTON,
+ Roles.TOGGLE_BUTTON,
+ Roles.BUTTONDROPDOWN,
+ Roles.BUTTONDROPDOWNGRID]),
+
+ Combobox: new BaseTraversalRule(
+ [Roles.COMBOBOX,
+ Roles.LISTBOX]),
+
+ Landmark: new BaseTraversalRule(
+ [],
+ function Landmark_match(aAccessible) {
+ return Utils.getLandmarkName(aAccessible) ? Filters.MATCH :
+ Filters.IGNORE;
+ }, null, true),
+
+ /* A rule for Android's section navigation, lands on landmarks, regions, and
+ on headings to aid navigation of traditionally structured documents */
+ Section: new BaseTraversalRule(
+ [],
+ function Section_match(aAccessible) {
+ if (aAccessible.role === Roles.HEADING) {
+ return Filters.MATCH;
+ }
+
+ let matchedRole = Utils.matchRoles(aAccessible, [
+ 'banner',
+ 'complementary',
+ 'contentinfo',
+ 'main',
+ 'navigation',
+ 'search',
+ 'region'
+ ]);
+
+ return matchedRole ? Filters.MATCH : Filters.IGNORE;
+ }, null, true),
+
+ Entry: new BaseTraversalRule(
+ [Roles.ENTRY,
+ Roles.PASSWORD_TEXT]),
+
+ FormElement: new BaseTraversalRule(
+ [Roles.PUSHBUTTON,
+ Roles.SPINBUTTON,
+ Roles.TOGGLE_BUTTON,
+ Roles.BUTTONDROPDOWN,
+ Roles.BUTTONDROPDOWNGRID,
+ Roles.COMBOBOX,
+ Roles.LISTBOX,
+ Roles.ENTRY,
+ Roles.PASSWORD_TEXT,
+ Roles.PAGETAB,
+ Roles.RADIOBUTTON,
+ Roles.RADIO_MENU_ITEM,
+ Roles.SLIDER,
+ Roles.CHECKBUTTON,
+ Roles.CHECK_MENU_ITEM,
+ Roles.SWITCH]),
+
+ Graphic: new BaseTraversalRule(
+ [Roles.GRAPHIC],
+ function Graphic_match(aAccessible) {
+ return TraversalRules._shouldSkipImage(aAccessible);
+ }),
+
+ Heading: new BaseTraversalRule(
+ [Roles.HEADING],
+ function Heading_match(aAccessible) {
+ return aAccessible.childCount > 0 ? Filters.MATCH : Filters.IGNORE;
+ }),
+
+ ListItem: new BaseTraversalRule(
+ [Roles.LISTITEM,
+ Roles.TERM]),
+
+ Link: new BaseTraversalRule(
+ [Roles.LINK],
+ function Link_match(aAccessible)
+ {
+ // We want to ignore anchors, only focus real links.
+ if (Utils.getState(aAccessible).contains(States.LINKED)) {
+ return Filters.MATCH;
+ } else {
+ return Filters.IGNORE;
+ }
+ }),
+
+ /* For TalkBack's "Control" granularity. Form conrols and links */
+ Control: new BaseTraversalRule(
+ [Roles.PUSHBUTTON,
+ Roles.SPINBUTTON,
+ Roles.TOGGLE_BUTTON,
+ Roles.BUTTONDROPDOWN,
+ Roles.BUTTONDROPDOWNGRID,
+ Roles.COMBOBOX,
+ Roles.LISTBOX,
+ Roles.ENTRY,
+ Roles.PASSWORD_TEXT,
+ Roles.PAGETAB,
+ Roles.RADIOBUTTON,
+ Roles.RADIO_MENU_ITEM,
+ Roles.SLIDER,
+ Roles.CHECKBUTTON,
+ Roles.CHECK_MENU_ITEM,
+ Roles.SWITCH,
+ Roles.LINK,
+ Roles.MENUITEM],
+ function Control_match(aAccessible)
+ {
+ // We want to ignore anchors, only focus real links.
+ if (aAccessible.role == Roles.LINK &&
+ !Utils.getState(aAccessible).contains(States.LINKED)) {
+ return Filters.IGNORE;
+ }
+ return Filters.MATCH;
+ }),
+
+ List: new BaseTraversalRule(
+ [Roles.LIST,
+ Roles.DEFINITION_LIST],
+ null, null, true),
+
+ PageTab: new BaseTraversalRule(
+ [Roles.PAGETAB]),
+
+ Paragraph: new BaseTraversalRule(
+ [Roles.PARAGRAPH,
+ Roles.SECTION],
+ function Paragraph_match(aAccessible) {
+ for (let child = aAccessible.firstChild; child; child = child.nextSibling) {
+ if (child.role === Roles.TEXT_LEAF) {
+ return Filters.MATCH | Filters.IGNORE_SUBTREE;
+ }
+ }
+
+ return Filters.IGNORE;
+ }),
+
+ RadioButton: new BaseTraversalRule(
+ [Roles.RADIOBUTTON,
+ Roles.RADIO_MENU_ITEM]),
+
+ Separator: new BaseTraversalRule(
+ [Roles.SEPARATOR]),
+
+ Table: new BaseTraversalRule(
+ [Roles.TABLE]),
+
+ Checkbox: new BaseTraversalRule(
+ [Roles.CHECKBUTTON,
+ Roles.CHECK_MENU_ITEM,
+ Roles.SWITCH /* A type of checkbox that represents on/off values */]),
+
+ _shouldSkipImage: function _shouldSkipImage(aAccessible) {
+ if (gSkipEmptyImages.value && aAccessible.name === '') {
+ return Filters.IGNORE;
+ }
+ return Filters.MATCH;
+ }
+};
+
+this.TraversalHelper = {
+ _helperPivotCache: null,
+
+ get helperPivotCache() {
+ delete this.helperPivotCache;
+ this.helperPivotCache = new WeakMap();
+ return this.helperPivotCache;
+ },
+
+ getHelperPivot: function TraversalHelper_getHelperPivot(aRoot) {
+ let pivot = this.helperPivotCache.get(aRoot.DOMNode);
+ if (!pivot) {
+ pivot = Utils.AccService.createAccessiblePivot(aRoot);
+ this.helperPivotCache.set(aRoot.DOMNode, pivot);
+ }
+
+ return pivot;
+ },
+
+ move: function TraversalHelper_move(aVirtualCursor, aMethod, aRule) {
+ let rule = TraversalRules[aRule];
+
+ if (rule.containerRule) {
+ let moved = false;
+ let helperPivot = this.getHelperPivot(aVirtualCursor.root);
+ helperPivot.position = aVirtualCursor.position;
+
+ // We continue to step through containers until there is one with an
+ // atomic child (via 'Simple') on which we could land.
+ while (!moved) {
+ if (helperPivot[aMethod](rule)) {
+ aVirtualCursor.modalRoot = helperPivot.position;
+ moved = aVirtualCursor.moveFirst(TraversalRules.Simple);
+ aVirtualCursor.modalRoot = null;
+ } else {
+ // If we failed to step to another container, break and return false.
+ break;
+ }
+ }
+
+ return moved;
+ } else {
+ return aVirtualCursor[aMethod](rule);
+ }
+ }
+
+};
diff --git a/accessible/jsat/Utils.jsm b/accessible/jsat/Utils.jsm
new file mode 100644
index 000000000..4e478cab0
--- /dev/null
+++ b/accessible/jsat/Utils.jsm
@@ -0,0 +1,1114 @@
+/* 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/. */
+
+/* global Components, XPCOMUtils, Services, PluralForm, Logger, Rect, Utils,
+ States, Relations, Roles, dump, Events, PivotContext, PrefCache */
+/* exported Utils, Logger, PivotContext, PrefCache, SettingCache */
+
+'use strict';
+
+const {classes: Cc, utils: Cu, interfaces: Ci} = Components;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Services', // jshint ignore:line
+ 'resource://gre/modules/Services.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Rect', // jshint ignore:line
+ 'resource://gre/modules/Geometry.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Roles', // jshint ignore:line
+ 'resource://gre/modules/accessibility/Constants.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Events', // jshint ignore:line
+ 'resource://gre/modules/accessibility/Constants.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Relations', // jshint ignore:line
+ 'resource://gre/modules/accessibility/Constants.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'States', // jshint ignore:line
+ 'resource://gre/modules/accessibility/Constants.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'PluralForm', // jshint ignore:line
+ 'resource://gre/modules/PluralForm.jsm');
+
+this.EXPORTED_SYMBOLS = ['Utils', 'Logger', 'PivotContext', 'PrefCache', // jshint ignore:line
+ 'SettingCache'];
+
+this.Utils = { // jshint ignore:line
+ _buildAppMap: {
+ '{3c2e2abc-06d4-11e1-ac3b-374f68613e61}': 'b2g',
+ '{d1bfe7d9-c01e-4237-998b-7b5f960a4314}': 'graphene',
+ '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}': 'browser',
+ '{aa3c5121-dab2-40e2-81ca-7ea25febc110}': 'mobile/android',
+ '{a23983c0-fd0e-11dc-95ff-0800200c9a66}': 'mobile/xul'
+ },
+
+ init: function Utils_init(aWindow) {
+ if (this._win) {
+ // XXX: only supports attaching to one window now.
+ throw new Error('Only one top-level window could used with AccessFu');
+ }
+ this._win = Cu.getWeakReference(aWindow);
+ },
+
+ uninit: function Utils_uninit() {
+ if (!this._win) {
+ return;
+ }
+ delete this._win;
+ },
+
+ get win() {
+ if (!this._win) {
+ return null;
+ }
+ return this._win.get();
+ },
+
+ get winUtils() {
+ let win = this.win;
+ if (!win) {
+ return null;
+ }
+ return win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(
+ Ci.nsIDOMWindowUtils);
+ },
+
+ get AccService() {
+ if (!this._AccService) {
+ this._AccService = Cc['@mozilla.org/accessibilityService;1'].
+ getService(Ci.nsIAccessibilityService);
+ }
+
+ return this._AccService;
+ },
+
+ set MozBuildApp(value) {
+ this._buildApp = value;
+ },
+
+ get MozBuildApp() {
+ if (!this._buildApp) {
+ this._buildApp = this._buildAppMap[Services.appinfo.ID];
+ }
+ return this._buildApp;
+ },
+
+ get OS() {
+ if (!this._OS) {
+ this._OS = Services.appinfo.OS;
+ }
+ return this._OS;
+ },
+
+ get widgetToolkit() {
+ if (!this._widgetToolkit) {
+ this._widgetToolkit = Services.appinfo.widgetToolkit;
+ }
+ return this._widgetToolkit;
+ },
+
+ get ScriptName() {
+ if (!this._ScriptName) {
+ this._ScriptName =
+ (Services.appinfo.processType == 2) ? 'AccessFuContent' : 'AccessFu';
+ }
+ return this._ScriptName;
+ },
+
+ get AndroidSdkVersion() {
+ if (!this._AndroidSdkVersion) {
+ if (Services.appinfo.OS == 'Android') {
+ this._AndroidSdkVersion = Services.sysinfo.getPropertyAsInt32(
+ 'version');
+ } else {
+ // Most useful in desktop debugging.
+ this._AndroidSdkVersion = 16;
+ }
+ }
+ return this._AndroidSdkVersion;
+ },
+
+ set AndroidSdkVersion(value) {
+ // When we want to mimic another version.
+ this._AndroidSdkVersion = value;
+ },
+
+ get BrowserApp() {
+ if (!this.win) {
+ return null;
+ }
+ switch (this.MozBuildApp) {
+ case 'mobile/android':
+ return this.win.BrowserApp;
+ case 'browser':
+ return this.win.gBrowser;
+ case 'b2g':
+ return this.win.shell;
+ default:
+ return null;
+ }
+ },
+
+ get CurrentBrowser() {
+ if (!this.BrowserApp) {
+ return null;
+ }
+ if (this.MozBuildApp == 'b2g') {
+ return this.BrowserApp.contentBrowser;
+ }
+ return this.BrowserApp.selectedBrowser;
+ },
+
+ get CurrentContentDoc() {
+ let browser = this.CurrentBrowser;
+ return browser ? browser.contentDocument : null;
+ },
+
+ get AllMessageManagers() {
+ let messageManagers = new Set();
+
+ function collectLeafMessageManagers(mm) {
+ for (let i = 0; i < mm.childCount; i++) {
+ let childMM = mm.getChildAt(i);
+
+ if ('sendAsyncMessage' in childMM) {
+ messageManagers.add(childMM);
+ } else {
+ collectLeafMessageManagers(childMM);
+ }
+ }
+ }
+
+ collectLeafMessageManagers(this.win.messageManager);
+
+ let document = this.CurrentContentDoc;
+
+ if (document) {
+ if (document.location.host === 'b2g') {
+ // The document is a b2g app chrome (ie. Mulet).
+ let contentBrowser = this.win.content.shell.contentBrowser;
+ messageManagers.add(this.getMessageManager(contentBrowser));
+ document = contentBrowser.contentDocument;
+ }
+
+ let remoteframes = document.querySelectorAll('iframe');
+
+ for (let i = 0; i < remoteframes.length; ++i) {
+ let mm = this.getMessageManager(remoteframes[i]);
+ if (mm) {
+ messageManagers.add(mm);
+ }
+ }
+
+ }
+
+ return messageManagers;
+ },
+
+ get isContentProcess() {
+ delete this.isContentProcess;
+ this.isContentProcess =
+ Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
+ return this.isContentProcess;
+ },
+
+ localize: function localize(aOutput) {
+ let outputArray = Array.isArray(aOutput) ? aOutput : [aOutput];
+ let localized =
+ outputArray.map(details => this.stringBundle.get(details));
+ // Clean up the white space.
+ return localized.filter(word => word).map(word => word.trim()).
+ filter(trimmed => trimmed);
+ },
+
+ get stringBundle() {
+ delete this.stringBundle;
+ let bundle = Services.strings.createBundle(
+ 'chrome://global/locale/AccessFu.properties');
+ this.stringBundle = {
+ get: function stringBundle_get(aDetails = {}) {
+ if (!aDetails || typeof aDetails === 'string') {
+ return aDetails;
+ }
+ let str = '';
+ let string = aDetails.string;
+ if (!string) {
+ return str;
+ }
+ try {
+ let args = aDetails.args;
+ let count = aDetails.count;
+ if (args) {
+ str = bundle.formatStringFromName(string, args, args.length);
+ } else {
+ str = bundle.GetStringFromName(string);
+ }
+ if (count) {
+ str = PluralForm.get(count, str);
+ str = str.replace('#1', count);
+ }
+ } catch (e) {
+ Logger.debug('Failed to get a string from a bundle for', string);
+ } finally {
+ return str;
+ }
+ }
+ };
+ return this.stringBundle;
+ },
+
+ getMessageManager: function getMessageManager(aBrowser) {
+ try {
+ return aBrowser.QueryInterface(Ci.nsIFrameLoaderOwner).
+ frameLoader.messageManager;
+ } catch (x) {
+ return null;
+ }
+ },
+
+ getState: function getState(aAccessibleOrEvent) {
+ if (aAccessibleOrEvent instanceof Ci.nsIAccessibleStateChangeEvent) {
+ return new State(
+ aAccessibleOrEvent.isExtraState ? 0 : aAccessibleOrEvent.state,
+ aAccessibleOrEvent.isExtraState ? aAccessibleOrEvent.state : 0);
+ } else {
+ let state = {};
+ let extState = {};
+ aAccessibleOrEvent.getState(state, extState);
+ return new State(state.value, extState.value);
+ }
+ },
+
+ getAttributes: function getAttributes(aAccessible) {
+ let attributes = {};
+
+ if (aAccessible && aAccessible.attributes) {
+ let attributesEnum = aAccessible.attributes.enumerate();
+
+ // Populate |attributes| object with |aAccessible|'s attribute key-value
+ // pairs.
+ while (attributesEnum.hasMoreElements()) {
+ let attribute = attributesEnum.getNext().QueryInterface(
+ Ci.nsIPropertyElement);
+ attributes[attribute.key] = attribute.value;
+ }
+ }
+
+ return attributes;
+ },
+
+ getVirtualCursor: function getVirtualCursor(aDocument) {
+ let doc = (aDocument instanceof Ci.nsIAccessible) ? aDocument :
+ this.AccService.getAccessibleFor(aDocument);
+
+ return doc.QueryInterface(Ci.nsIAccessibleDocument).virtualCursor;
+ },
+
+ getContentResolution: function _getContentResolution(aAccessible) {
+ let res = { value: 1 };
+ aAccessible.document.window.QueryInterface(
+ Ci.nsIInterfaceRequestor).getInterface(
+ Ci.nsIDOMWindowUtils).getResolution(res);
+ return res.value;
+ },
+
+ getBounds: function getBounds(aAccessible, aPreserveContentScale) {
+ let objX = {}, objY = {}, objW = {}, objH = {};
+ aAccessible.getBounds(objX, objY, objW, objH);
+
+ let scale = aPreserveContentScale ? 1 :
+ this.getContentResolution(aAccessible);
+
+ return new Rect(objX.value, objY.value, objW.value, objH.value).scale(
+ scale, scale);
+ },
+
+ getTextBounds: function getTextBounds(aAccessible, aStart, aEnd,
+ aPreserveContentScale) {
+ let accText = aAccessible.QueryInterface(Ci.nsIAccessibleText);
+ let objX = {}, objY = {}, objW = {}, objH = {};
+ accText.getRangeExtents(aStart, aEnd, objX, objY, objW, objH,
+ Ci.nsIAccessibleCoordinateType.COORDTYPE_SCREEN_RELATIVE);
+
+ let scale = aPreserveContentScale ? 1 :
+ this.getContentResolution(aAccessible);
+
+ return new Rect(objX.value, objY.value, objW.value, objH.value).scale(
+ scale, scale);
+ },
+
+ /**
+ * Get current display DPI.
+ */
+ get dpi() {
+ delete this.dpi;
+ this.dpi = this.winUtils.displayDPI;
+ return this.dpi;
+ },
+
+ isInSubtree: function isInSubtree(aAccessible, aSubTreeRoot) {
+ let acc = aAccessible;
+
+ // If aSubTreeRoot is an accessible document, we will only walk up the
+ // ancestry of documents and skip everything else.
+ if (aSubTreeRoot instanceof Ci.nsIAccessibleDocument) {
+ while (acc) {
+ let parentDoc = acc instanceof Ci.nsIAccessibleDocument ?
+ acc.parentDocument : acc.document;
+ if (parentDoc === aSubTreeRoot) {
+ return true;
+ }
+ acc = parentDoc;
+ }
+ return false;
+ }
+
+ while (acc) {
+ if (acc == aSubTreeRoot) {
+ return true;
+ }
+
+ try {
+ acc = acc.parent;
+ } catch (x) {
+ Logger.debug('Failed to get parent:', x);
+ acc = null;
+ }
+ }
+
+ return false;
+ },
+
+ isHidden: function isHidden(aAccessible) {
+ // Need to account for aria-hidden, so can't just check for INVISIBLE
+ // state.
+ let hidden = Utils.getAttributes(aAccessible).hidden;
+ return hidden && hidden === 'true';
+ },
+
+ visibleChildCount: function visibleChildCount(aAccessible) {
+ let count = 0;
+ for (let child = aAccessible.firstChild; child; child = child.nextSibling) {
+ if (!this.isHidden(child)) {
+ ++count;
+ }
+ }
+ return count;
+ },
+
+ inHiddenSubtree: function inHiddenSubtree(aAccessible) {
+ for (let acc=aAccessible; acc; acc=acc.parent) {
+ if (this.isHidden(acc)) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ isAliveAndVisible: function isAliveAndVisible(aAccessible, aIsOnScreen) {
+ if (!aAccessible) {
+ return false;
+ }
+
+ try {
+ let state = this.getState(aAccessible);
+ if (state.contains(States.DEFUNCT) || state.contains(States.INVISIBLE) ||
+ (aIsOnScreen && state.contains(States.OFFSCREEN)) ||
+ Utils.inHiddenSubtree(aAccessible)) {
+ return false;
+ }
+ } catch (x) {
+ return false;
+ }
+
+ return true;
+ },
+
+ matchAttributeValue: function matchAttributeValue(aAttributeValue, values) {
+ let attrSet = new Set(aAttributeValue.split(' '));
+ for (let value of values) {
+ if (attrSet.has(value)) {
+ return value;
+ }
+ }
+ },
+
+ getLandmarkName: function getLandmarkName(aAccessible) {
+ return this.matchRoles(aAccessible, [
+ 'banner',
+ 'complementary',
+ 'contentinfo',
+ 'main',
+ 'navigation',
+ 'search'
+ ]);
+ },
+
+ getMathRole: function getMathRole(aAccessible) {
+ return this.matchRoles(aAccessible, [
+ 'base',
+ 'close-fence',
+ 'denominator',
+ 'numerator',
+ 'open-fence',
+ 'overscript',
+ 'presubscript',
+ 'presuperscript',
+ 'root-index',
+ 'subscript',
+ 'superscript',
+ 'underscript'
+ ]);
+ },
+
+ matchRoles: function matchRoles(aAccessible, aRoles) {
+ let roles = this.getAttributes(aAccessible)['xml-roles'];
+ if (!roles) {
+ return;
+ }
+
+ // Looking up a role that would match any in the provided roles.
+ return this.matchAttributeValue(roles, aRoles);
+ },
+
+ getEmbeddedControl: function getEmbeddedControl(aLabel) {
+ if (aLabel) {
+ let relation = aLabel.getRelationByType(Relations.LABEL_FOR);
+ for (let i = 0; i < relation.targetsCount; i++) {
+ let target = relation.getTarget(i);
+ if (target.parent === aLabel) {
+ return target;
+ }
+ }
+ }
+
+ return null;
+ },
+
+ isListItemDecorator: function isListItemDecorator(aStaticText,
+ aExcludeOrdered) {
+ let parent = aStaticText.parent;
+ if (aExcludeOrdered && parent.parent.DOMNode.nodeName === 'OL') {
+ return false;
+ }
+
+ return parent.role === Roles.LISTITEM && parent.childCount > 1 &&
+ aStaticText.indexInParent === 0;
+ },
+
+ dispatchChromeEvent: function dispatchChromeEvent(aType, aDetails) {
+ let details = {
+ type: aType,
+ details: JSON.stringify(
+ typeof aDetails === 'string' ? { eventType : aDetails } : aDetails)
+ };
+ let window = this.win;
+ let shell = window.shell || window.content.shell;
+ if (shell) {
+ // On B2G device.
+ shell.sendChromeEvent(details);
+ } else {
+ // Dispatch custom event to have support for desktop and screen reader
+ // emulator add-on.
+ window.dispatchEvent(new window.CustomEvent(aType, {
+ bubbles: true,
+ cancelable: true,
+ detail: details
+ }));
+ }
+
+ },
+
+ isActivatableOnFingerUp: function isActivatableOnFingerUp(aAccessible) {
+ if (aAccessible.role === Roles.KEY) {
+ return true;
+ }
+ let quick_activate = this.getAttributes(aAccessible)['moz-quick-activate'];
+ return quick_activate && JSON.parse(quick_activate);
+ }
+};
+
+/**
+ * State object used internally to process accessible's states.
+ * @param {Number} aBase Base state.
+ * @param {Number} aExtended Extended state.
+ */
+function State(aBase, aExtended) {
+ this.base = aBase;
+ this.extended = aExtended;
+}
+
+State.prototype = {
+ contains: function State_contains(other) {
+ return !!(this.base & other.base || this.extended & other.extended);
+ },
+ toString: function State_toString() {
+ let stateStrings = Utils.AccService.
+ getStringStates(this.base, this.extended);
+ let statesArray = new Array(stateStrings.length);
+ for (let i = 0; i < statesArray.length; i++) {
+ statesArray[i] = stateStrings.item(i);
+ }
+ return '[' + statesArray.join(', ') + ']';
+ }
+};
+
+this.Logger = { // jshint ignore:line
+ GESTURE: -1,
+ DEBUG: 0,
+ INFO: 1,
+ WARNING: 2,
+ ERROR: 3,
+ _LEVEL_NAMES: ['GESTURE', 'DEBUG', 'INFO', 'WARNING', 'ERROR'],
+
+ logLevel: 1, // INFO;
+
+ test: false,
+
+ log: function log(aLogLevel) {
+ if (aLogLevel < this.logLevel) {
+ return;
+ }
+
+ let args = Array.prototype.slice.call(arguments, 1);
+ let message = (typeof(args[0]) === 'function' ? args[0]() : args).join(' ');
+ message = '[' + Utils.ScriptName + '] ' + this._LEVEL_NAMES[aLogLevel + 1] +
+ ' ' + message + '\n';
+ dump(message);
+ // Note: used for testing purposes. If |this.test| is true, also log to
+ // the console service.
+ if (this.test) {
+ try {
+ Services.console.logStringMessage(message);
+ } catch (ex) {
+ // There was an exception logging to the console service.
+ }
+ }
+ },
+
+ info: function info() {
+ this.log.apply(
+ this, [this.INFO].concat(Array.prototype.slice.call(arguments)));
+ },
+
+ gesture: function gesture() {
+ this.log.apply(
+ this, [this.GESTURE].concat(Array.prototype.slice.call(arguments)));
+ },
+
+ debug: function debug() {
+ this.log.apply(
+ this, [this.DEBUG].concat(Array.prototype.slice.call(arguments)));
+ },
+
+ warning: function warning() {
+ this.log.apply(
+ this, [this.WARNING].concat(Array.prototype.slice.call(arguments)));
+ },
+
+ error: function error() {
+ this.log.apply(
+ this, [this.ERROR].concat(Array.prototype.slice.call(arguments)));
+ },
+
+ logException: function logException(
+ aException, aErrorMessage = 'An exception has occured') {
+ try {
+ let stackMessage = '';
+ if (aException.stack) {
+ stackMessage = ' ' + aException.stack.replace(/\n/g, '\n ');
+ } else if (aException.location) {
+ let frame = aException.location;
+ let stackLines = [];
+ while (frame && frame.lineNumber) {
+ stackLines.push(
+ ' ' + frame.name + '@' + frame.filename + ':' + frame.lineNumber);
+ frame = frame.caller;
+ }
+ stackMessage = stackLines.join('\n');
+ } else {
+ stackMessage =
+ '(' + aException.fileName + ':' + aException.lineNumber + ')';
+ }
+ this.error(aErrorMessage + ':\n ' +
+ aException.message + '\n' +
+ stackMessage);
+ } catch (x) {
+ this.error(x);
+ }
+ },
+
+ accessibleToString: function accessibleToString(aAccessible) {
+ if (!aAccessible) {
+ return '[ null ]';
+ }
+
+ try {
+ return'[ ' + Utils.AccService.getStringRole(aAccessible.role) +
+ ' | ' + aAccessible.name + ' ]';
+ } catch (x) {
+ return '[ defunct ]';
+ }
+ },
+
+ eventToString: function eventToString(aEvent) {
+ let str = Utils.AccService.getStringEventType(aEvent.eventType);
+ if (aEvent.eventType == Events.STATE_CHANGE) {
+ let event = aEvent.QueryInterface(Ci.nsIAccessibleStateChangeEvent);
+ let stateStrings = event.isExtraState ?
+ Utils.AccService.getStringStates(0, event.state) :
+ Utils.AccService.getStringStates(event.state, 0);
+ str += ' (' + stateStrings.item(0) + ')';
+ }
+
+ if (aEvent.eventType == Events.VIRTUALCURSOR_CHANGED) {
+ let event = aEvent.QueryInterface(
+ Ci.nsIAccessibleVirtualCursorChangeEvent);
+ let pivot = aEvent.accessible.QueryInterface(
+ Ci.nsIAccessibleDocument).virtualCursor;
+ str += ' (' + this.accessibleToString(event.oldAccessible) + ' -> ' +
+ this.accessibleToString(pivot.position) + ')';
+ }
+
+ return str;
+ },
+
+ statesToString: function statesToString(aAccessible) {
+ return Utils.getState(aAccessible).toString();
+ },
+
+ dumpTree: function dumpTree(aLogLevel, aRootAccessible) {
+ if (aLogLevel < this.logLevel) {
+ return;
+ }
+
+ this._dumpTreeInternal(aLogLevel, aRootAccessible, 0);
+ },
+
+ _dumpTreeInternal:
+ function _dumpTreeInternal(aLogLevel, aAccessible, aIndent) {
+ let indentStr = '';
+ for (let i = 0; i < aIndent; i++) {
+ indentStr += ' ';
+ }
+ this.log(aLogLevel, indentStr,
+ this.accessibleToString(aAccessible),
+ '(' + this.statesToString(aAccessible) + ')');
+ for (let i = 0; i < aAccessible.childCount; i++) {
+ this._dumpTreeInternal(aLogLevel, aAccessible.getChildAt(i),
+ aIndent + 1);
+ }
+ }
+};
+
+/**
+ * PivotContext: An object that generates and caches context information
+ * for a given accessible and its relationship with another accessible.
+ *
+ * If the given accessible is a label for a nested control, then this
+ * context will represent the nested control instead of the label.
+ * With the exception of bounds calculation, which will use the containing
+ * label. In this case the |accessible| field would be the embedded control,
+ * and the |accessibleForBounds| field would be the label.
+ */
+this.PivotContext = function PivotContext(aAccessible, aOldAccessible, // jshint ignore:line
+ aStartOffset, aEndOffset, aIgnoreAncestry = false,
+ aIncludeInvisible = false) {
+ this._accessible = aAccessible;
+ this._nestedControl = Utils.getEmbeddedControl(aAccessible);
+ this._oldAccessible =
+ this._isDefunct(aOldAccessible) ? null : aOldAccessible;
+ this.startOffset = aStartOffset;
+ this.endOffset = aEndOffset;
+ this._ignoreAncestry = aIgnoreAncestry;
+ this._includeInvisible = aIncludeInvisible;
+};
+
+PivotContext.prototype = {
+ get accessible() {
+ // If the current pivot accessible has a nested control,
+ // make this context use it publicly.
+ return this._nestedControl || this._accessible;
+ },
+
+ get oldAccessible() {
+ return this._oldAccessible;
+ },
+
+ get isNestedControl() {
+ return !!this._nestedControl;
+ },
+
+ get accessibleForBounds() {
+ return this._accessible;
+ },
+
+ get textAndAdjustedOffsets() {
+ if (this.startOffset === -1 && this.endOffset === -1) {
+ return null;
+ }
+
+ if (!this._textAndAdjustedOffsets) {
+ let result = {startOffset: this.startOffset,
+ endOffset: this.endOffset,
+ text: this._accessible.QueryInterface(Ci.nsIAccessibleText).
+ getText(0,
+ Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT)};
+ let hypertextAcc = this._accessible.QueryInterface(
+ Ci.nsIAccessibleHyperText);
+
+ // Iterate through the links in backwards order so text replacements don't
+ // affect the offsets of links yet to be processed.
+ for (let i = hypertextAcc.linkCount - 1; i >= 0; i--) {
+ let link = hypertextAcc.getLinkAt(i);
+ let linkText = '';
+ if (link instanceof Ci.nsIAccessibleText) {
+ linkText = link.QueryInterface(Ci.nsIAccessibleText).
+ getText(0,
+ Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT);
+ }
+
+ let start = link.startIndex;
+ let end = link.endIndex;
+ for (let offset of ['startOffset', 'endOffset']) {
+ if (this[offset] >= end) {
+ result[offset] += linkText.length - (end - start);
+ }
+ }
+ result.text = result.text.substring(0, start) + linkText +
+ result.text.substring(end);
+ }
+
+ this._textAndAdjustedOffsets = result;
+ }
+
+ return this._textAndAdjustedOffsets;
+ },
+
+ /**
+ * Get a list of |aAccessible|'s ancestry up to the root.
+ * @param {nsIAccessible} aAccessible.
+ * @return {Array} Ancestry list.
+ */
+ _getAncestry: function _getAncestry(aAccessible) {
+ let ancestry = [];
+ let parent = aAccessible;
+ try {
+ while (parent && (parent = parent.parent)) {
+ ancestry.push(parent);
+ }
+ } catch (x) {
+ // A defunct accessible will raise an exception geting parent.
+ Logger.debug('Failed to get parent:', x);
+ }
+ return ancestry.reverse();
+ },
+
+ /**
+ * A list of the old accessible's ancestry.
+ */
+ get oldAncestry() {
+ if (!this._oldAncestry) {
+ if (!this._oldAccessible || this._ignoreAncestry) {
+ this._oldAncestry = [];
+ } else {
+ this._oldAncestry = this._getAncestry(this._oldAccessible);
+ this._oldAncestry.push(this._oldAccessible);
+ }
+ }
+ return this._oldAncestry;
+ },
+
+ /**
+ * A list of the current accessible's ancestry.
+ */
+ get currentAncestry() {
+ if (!this._currentAncestry) {
+ this._currentAncestry = this._ignoreAncestry ? [] :
+ this._getAncestry(this.accessible);
+ }
+ return this._currentAncestry;
+ },
+
+ /*
+ * This is a list of the accessible's ancestry up to the common ancestor
+ * of the accessible and the old accessible. It is useful for giving the
+ * user context as to where they are in the heirarchy.
+ */
+ get newAncestry() {
+ if (!this._newAncestry) {
+ this._newAncestry = this._ignoreAncestry ? [] :
+ this.currentAncestry.filter(
+ (currentAncestor, i) => currentAncestor !== this.oldAncestry[i]);
+ }
+ return this._newAncestry;
+ },
+
+ /*
+ * Traverse the accessible's subtree in pre or post order.
+ * It only includes the accessible's visible chidren.
+ * Note: needSubtree is a function argument that can be used to determine
+ * whether aAccessible's subtree is required.
+ */
+ _traverse: function* _traverse(aAccessible, aPreorder, aStop) {
+ if (aStop && aStop(aAccessible)) {
+ return;
+ }
+ let child = aAccessible.firstChild;
+ while (child) {
+ let include;
+ if (this._includeInvisible) {
+ include = true;
+ } else {
+ include = !Utils.isHidden(child);
+ }
+ if (include) {
+ if (aPreorder) {
+ yield child;
+ for (let node of this._traverse(child, aPreorder, aStop)) {
+ yield node;
+ }
+ } else {
+ for (let node of this._traverse(child, aPreorder, aStop)) {
+ yield node;
+ }
+ yield child;
+ }
+ }
+ child = child.nextSibling;
+ }
+ },
+
+ /**
+ * Get interaction hints for the context ancestry.
+ * @return {Array} Array of interaction hints.
+ */
+ get interactionHints() {
+ let hints = [];
+ this.newAncestry.concat(this.accessible).reverse().forEach(aAccessible => {
+ let hint = Utils.getAttributes(aAccessible)['moz-hint'];
+ if (hint) {
+ hints.push(hint);
+ } else if (aAccessible.actionCount > 0) {
+ hints.push({
+ string: Utils.AccService.getStringRole(
+ aAccessible.role).replace(/\s/g, '') + '-hint'
+ });
+ }
+ });
+ return hints;
+ },
+
+ /*
+ * A subtree generator function, used to generate a flattened
+ * list of the accessible's subtree in pre or post order.
+ * It only includes the accessible's visible chidren.
+ * @param {boolean} aPreorder A flag for traversal order. If true, traverse
+ * in preorder; if false, traverse in postorder.
+ * @param {function} aStop An optional function, indicating whether subtree
+ * traversal should stop.
+ */
+ subtreeGenerator: function subtreeGenerator(aPreorder, aStop) {
+ return this._traverse(this.accessible, aPreorder, aStop);
+ },
+
+ getCellInfo: function getCellInfo(aAccessible) {
+ if (!this._cells) {
+ this._cells = new WeakMap();
+ }
+
+ let domNode = aAccessible.DOMNode;
+ if (this._cells.has(domNode)) {
+ return this._cells.get(domNode);
+ }
+
+ let cellInfo = {};
+ let getAccessibleCell = function getAccessibleCell(aAccessible) {
+ if (!aAccessible) {
+ return null;
+ }
+ if ([
+ Roles.CELL,
+ Roles.COLUMNHEADER,
+ Roles.ROWHEADER,
+ Roles.MATHML_CELL
+ ].indexOf(aAccessible.role) < 0) {
+ return null;
+ }
+ try {
+ return aAccessible.QueryInterface(Ci.nsIAccessibleTableCell);
+ } catch (x) {
+ Logger.logException(x);
+ return null;
+ }
+ };
+ let getHeaders = function* getHeaders(aHeaderCells) {
+ let enumerator = aHeaderCells.enumerate();
+ while (enumerator.hasMoreElements()) {
+ yield enumerator.getNext().QueryInterface(Ci.nsIAccessible).name;
+ }
+ };
+
+ cellInfo.current = getAccessibleCell(aAccessible);
+
+ if (!cellInfo.current) {
+ Logger.warning(aAccessible,
+ 'does not support nsIAccessibleTableCell interface.');
+ this._cells.set(domNode, null);
+ return null;
+ }
+
+ let table = cellInfo.current.table;
+ if (table.isProbablyForLayout()) {
+ this._cells.set(domNode, null);
+ return null;
+ }
+
+ cellInfo.previous = null;
+ let oldAncestry = this.oldAncestry.reverse();
+ let ancestor = oldAncestry.shift();
+ while (!cellInfo.previous && ancestor) {
+ let cell = getAccessibleCell(ancestor);
+ if (cell && cell.table === table) {
+ cellInfo.previous = cell;
+ }
+ ancestor = oldAncestry.shift();
+ }
+
+ if (cellInfo.previous) {
+ cellInfo.rowChanged = cellInfo.current.rowIndex !==
+ cellInfo.previous.rowIndex;
+ cellInfo.columnChanged = cellInfo.current.columnIndex !==
+ cellInfo.previous.columnIndex;
+ } else {
+ cellInfo.rowChanged = true;
+ cellInfo.columnChanged = true;
+ }
+
+ cellInfo.rowExtent = cellInfo.current.rowExtent;
+ cellInfo.columnExtent = cellInfo.current.columnExtent;
+ cellInfo.columnIndex = cellInfo.current.columnIndex;
+ cellInfo.rowIndex = cellInfo.current.rowIndex;
+
+ cellInfo.columnHeaders = [];
+ if (cellInfo.columnChanged && cellInfo.current.role !==
+ Roles.COLUMNHEADER) {
+ cellInfo.columnHeaders = [...getHeaders(cellInfo.current.columnHeaderCells)];
+ }
+ cellInfo.rowHeaders = [];
+ if (cellInfo.rowChanged &&
+ (cellInfo.current.role === Roles.CELL ||
+ cellInfo.current.role === Roles.MATHML_CELL)) {
+ cellInfo.rowHeaders = [...getHeaders(cellInfo.current.rowHeaderCells)];
+ }
+
+ this._cells.set(domNode, cellInfo);
+ return cellInfo;
+ },
+
+ get bounds() {
+ if (!this._bounds) {
+ this._bounds = Utils.getBounds(this.accessibleForBounds);
+ }
+
+ return this._bounds.clone();
+ },
+
+ _isDefunct: function _isDefunct(aAccessible) {
+ try {
+ return Utils.getState(aAccessible).contains(States.DEFUNCT);
+ } catch (x) {
+ return true;
+ }
+ }
+};
+
+this.PrefCache = function PrefCache(aName, aCallback, aRunCallbackNow) { // jshint ignore:line
+ this.name = aName;
+ this.callback = aCallback;
+
+ let branch = Services.prefs;
+ this.value = this._getValue(branch);
+
+ if (this.callback && aRunCallbackNow) {
+ try {
+ this.callback(this.name, this.value, true);
+ } catch (x) {
+ Logger.logException(x);
+ }
+ }
+
+ branch.addObserver(aName, this, true);
+};
+
+PrefCache.prototype = {
+ _getValue: function _getValue(aBranch) {
+ try {
+ if (!this.type) {
+ this.type = aBranch.getPrefType(this.name);
+ }
+ switch (this.type) {
+ case Ci.nsIPrefBranch.PREF_STRING:
+ return aBranch.getCharPref(this.name);
+ case Ci.nsIPrefBranch.PREF_INT:
+ return aBranch.getIntPref(this.name);
+ case Ci.nsIPrefBranch.PREF_BOOL:
+ return aBranch.getBoolPref(this.name);
+ default:
+ return null;
+ }
+ } catch (x) {
+ // Pref does not exist.
+ return null;
+ }
+ },
+
+ observe: function observe(aSubject) {
+ this.value = this._getValue(aSubject.QueryInterface(Ci.nsIPrefBranch));
+ Logger.info('pref changed', this.name, this.value);
+ if (this.callback) {
+ try {
+ this.callback(this.name, this.value, false);
+ } catch (x) {
+ Logger.logException(x);
+ }
+ }
+ },
+
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference])
+};
+
+this.SettingCache = function SettingCache(aName, aCallback, aOptions = {}) { // jshint ignore:line
+ this.value = aOptions.defaultValue;
+ let runCallback = () => {
+ if (aCallback) {
+ aCallback(aName, this.value);
+ if (aOptions.callbackOnce) {
+ runCallback = () => {};
+ }
+ }
+ };
+
+ let settings = Utils.win.navigator.mozSettings;
+ if (!settings) {
+ if (aOptions.callbackNow) {
+ runCallback();
+ }
+ return;
+ }
+
+
+ let lock = settings.createLock();
+ let req = lock.get(aName);
+
+ req.addEventListener('success', () => {
+ this.value = req.result[aName] === undefined ?
+ aOptions.defaultValue : req.result[aName];
+ if (aOptions.callbackNow) {
+ runCallback();
+ }
+ });
+
+ settings.addObserver(aName,
+ (evt) => {
+ this.value = evt.settingValue;
+ runCallback();
+ });
+};
diff --git a/accessible/jsat/content-script.js b/accessible/jsat/content-script.js
new file mode 100644
index 000000000..6ef0dadc3
--- /dev/null
+++ b/accessible/jsat/content-script.js
@@ -0,0 +1,151 @@
+/* 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/. */
+
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Logger',
+ 'resource://gre/modules/accessibility/Utils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Presentation',
+ 'resource://gre/modules/accessibility/Presentation.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
+ 'resource://gre/modules/accessibility/Utils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'EventManager',
+ 'resource://gre/modules/accessibility/EventManager.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'ContentControl',
+ 'resource://gre/modules/accessibility/ContentControl.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Roles',
+ 'resource://gre/modules/accessibility/Constants.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'States',
+ 'resource://gre/modules/accessibility/Constants.jsm');
+
+Logger.info('content-script.js', content.document.location);
+
+var eventManager = null;
+var contentControl = null;
+
+function forwardToParent(aMessage) {
+ // XXX: This is a silly way to make a deep copy
+ let newJSON = JSON.parse(JSON.stringify(aMessage.json));
+ newJSON.origin = 'child';
+ sendAsyncMessage(aMessage.name, newJSON);
+}
+
+function forwardToChild(aMessage, aListener, aVCPosition) {
+ let acc = aVCPosition || Utils.getVirtualCursor(content.document).position;
+
+ if (!Utils.isAliveAndVisible(acc) || acc.role != Roles.INTERNAL_FRAME) {
+ return false;
+ }
+
+ Logger.debug(() => {
+ return ['forwardToChild', Logger.accessibleToString(acc),
+ aMessage.name, JSON.stringify(aMessage.json, null, ' ')];
+ });
+
+ let mm = Utils.getMessageManager(acc.DOMNode);
+
+ if (aListener) {
+ mm.addMessageListener(aMessage.name, aListener);
+ }
+
+ // XXX: This is a silly way to make a deep copy
+ let newJSON = JSON.parse(JSON.stringify(aMessage.json));
+ newJSON.origin = 'parent';
+ if (Utils.isContentProcess) {
+ // XXX: OOP content's screen offset is 0,
+ // so we remove the real screen offset here.
+ newJSON.x -= content.mozInnerScreenX;
+ newJSON.y -= content.mozInnerScreenY;
+ }
+ mm.sendAsyncMessage(aMessage.name, newJSON);
+ return true;
+}
+
+function activateContextMenu(aMessage) {
+ let position = Utils.getVirtualCursor(content.document).position;
+ if (!forwardToChild(aMessage, activateContextMenu, position)) {
+ let center = Utils.getBounds(position, true).center();
+
+ let evt = content.document.createEvent('HTMLEvents');
+ evt.initEvent('contextmenu', true, true);
+ evt.clientX = center.x;
+ evt.clientY = center.y;
+ position.DOMNode.dispatchEvent(evt);
+ }
+}
+
+function presentCaretChange(aText, aOldOffset, aNewOffset) {
+ if (aOldOffset !== aNewOffset) {
+ let msg = Presentation.textSelectionChanged(aText, aNewOffset, aNewOffset,
+ aOldOffset, aOldOffset, true);
+ sendAsyncMessage('AccessFu:Present', msg);
+ }
+}
+
+function scroll(aMessage) {
+ let position = Utils.getVirtualCursor(content.document).position;
+ if (!forwardToChild(aMessage, scroll, position)) {
+ sendAsyncMessage('AccessFu:DoScroll',
+ { bounds: Utils.getBounds(position, true),
+ page: aMessage.json.page,
+ horizontal: aMessage.json.horizontal });
+ }
+}
+
+addMessageListener(
+ 'AccessFu:Start',
+ function(m) {
+ if (m.json.logLevel) {
+ Logger.logLevel = Logger[m.json.logLevel];
+ }
+
+ Logger.debug('AccessFu:Start');
+ if (m.json.buildApp)
+ Utils.MozBuildApp = m.json.buildApp;
+
+ addMessageListener('AccessFu:ContextMenu', activateContextMenu);
+ addMessageListener('AccessFu:Scroll', scroll);
+
+ if (!contentControl) {
+ contentControl = new ContentControl(this);
+ }
+ contentControl.start();
+
+ if (!eventManager) {
+ eventManager = new EventManager(this, contentControl);
+ }
+ eventManager.inTest = m.json.inTest;
+ eventManager.start();
+
+ function contentStarted() {
+ let accDoc = Utils.AccService.getAccessibleFor(content.document);
+ if (accDoc && !Utils.getState(accDoc).contains(States.BUSY)) {
+ sendAsyncMessage('AccessFu:ContentStarted');
+ } else {
+ content.setTimeout(contentStarted, 0);
+ }
+ }
+
+ if (m.json.inTest) {
+ // During a test we want to wait for the document to finish loading for
+ // consistency.
+ contentStarted();
+ }
+ });
+
+addMessageListener(
+ 'AccessFu:Stop',
+ function(m) {
+ Logger.debug('AccessFu:Stop');
+
+ removeMessageListener('AccessFu:ContextMenu', activateContextMenu);
+ removeMessageListener('AccessFu:Scroll', scroll);
+
+ eventManager.stop();
+ contentControl.stop();
+ });
+
+sendAsyncMessage('AccessFu:Ready');
diff --git a/accessible/jsat/jar.mn b/accessible/jsat/jar.mn
new file mode 100644
index 000000000..970fb9a9b
--- /dev/null
+++ b/accessible/jsat/jar.mn
@@ -0,0 +1,10 @@
+# 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/.
+
+toolkit.jar:
+ content/global/accessibility/AccessFu.css (AccessFu.css)
+ content/global/accessibility/content-script.js (content-script.js)
+ content/global/accessibility/virtual_cursor_move.ogg (sounds/virtual_cursor_move.ogg)
+ content/global/accessibility/virtual_cursor_key.ogg (sounds/virtual_cursor_key.ogg)
+ content/global/accessibility/clicked.ogg (sounds/clicked.ogg)
diff --git a/accessible/jsat/moz.build b/accessible/jsat/moz.build
new file mode 100644
index 000000000..b9051f532
--- /dev/null
+++ b/accessible/jsat/moz.build
@@ -0,0 +1,20 @@
+# -*- 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/.
+
+EXTRA_JS_MODULES.accessibility += [
+ 'AccessFu.jsm',
+ 'Constants.jsm',
+ 'ContentControl.jsm',
+ 'EventManager.jsm',
+ 'Gestures.jsm',
+ 'OutputGenerator.jsm',
+ 'PointerAdapter.jsm',
+ 'Presentation.jsm',
+ 'Traversal.jsm',
+ 'Utils.jsm'
+]
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/accessible/jsat/sounds/clicked.ogg b/accessible/jsat/sounds/clicked.ogg
new file mode 100644
index 000000000..68388018e
--- /dev/null
+++ b/accessible/jsat/sounds/clicked.ogg
Binary files differ
diff --git a/accessible/jsat/sounds/virtual_cursor_key.ogg b/accessible/jsat/sounds/virtual_cursor_key.ogg
new file mode 100644
index 000000000..b29b55b44
--- /dev/null
+++ b/accessible/jsat/sounds/virtual_cursor_key.ogg
Binary files differ
diff --git a/accessible/jsat/sounds/virtual_cursor_move.ogg b/accessible/jsat/sounds/virtual_cursor_move.ogg
new file mode 100644
index 000000000..da9793460
--- /dev/null
+++ b/accessible/jsat/sounds/virtual_cursor_move.ogg
Binary files differ
diff --git a/accessible/mac/ARIAGridAccessibleWrap.h b/accessible/mac/ARIAGridAccessibleWrap.h
new file mode 100644
index 000000000..5d397e915
--- /dev/null
+++ b/accessible/mac/ARIAGridAccessibleWrap.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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_ARIAGRIDACCESSIBLEWRAP_H
+#define MOZILLA_A11Y_ARIAGRIDACCESSIBLEWRAP_H
+
+#include "ARIAGridAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class ARIAGridAccessible ARIAGridAccessibleWrap;
+typedef class ARIAGridCellAccessible ARIAGridCellAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/AccessibleWrap.h b/accessible/mac/AccessibleWrap.h
new file mode 100644
index 000000000..6c746ff0d
--- /dev/null
+++ b/accessible/mac/AccessibleWrap.h
@@ -0,0 +1,103 @@
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 _AccessibleWrap_H_
+#define _AccessibleWrap_H_
+
+#include <objc/objc.h>
+
+#include "Accessible.h"
+#include "States.h"
+
+#include "nsCOMPtr.h"
+
+#include "nsTArray.h"
+
+#if defined(__OBJC__)
+@class mozAccessible;
+#endif
+
+namespace mozilla {
+namespace a11y {
+
+class AccessibleWrap : public Accessible
+{
+public: // construction, destruction
+ AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc);
+ virtual ~AccessibleWrap();
+
+ /**
+ * Get the native Obj-C object (mozAccessible).
+ */
+ virtual void GetNativeInterface(void** aOutAccessible) override;
+
+ /**
+ * The objective-c |Class| type that this accessible's native object
+ * should be instantied with. used on runtime to determine the
+ * right type for this accessible's associated native object.
+ */
+ virtual Class GetNativeType ();
+
+ virtual void Shutdown () override;
+
+ virtual bool InsertChildAt(uint32_t aIdx, Accessible* aChild) override;
+ virtual bool RemoveChild(Accessible* aAccessible) override;
+
+ virtual nsresult HandleAccEvent(AccEvent* aEvent) override;
+
+protected:
+
+ /**
+ * Return true if the parent doesn't have children to expose to AT.
+ */
+ bool AncestorIsFlat();
+
+ /**
+ * Get the native object. Create it if needed.
+ */
+#if defined(__OBJC__)
+ mozAccessible* GetNativeObject();
+#else
+ id GetNativeObject();
+#endif
+
+private:
+
+ /**
+ * Our native object. Private because its creation is done lazily.
+ * Don't access it directly. Ever. Unless you are GetNativeObject() or
+ * Shutdown()
+ */
+#if defined(__OBJC__)
+ // if we are in Objective-C, we use the actual Obj-C class.
+ mozAccessible* mNativeObject;
+#else
+ id mNativeObject;
+#endif
+
+ /**
+ * We have created our native. This does not mean there is one.
+ * This can never go back to false.
+ * We need it because checking whether we need a native object cost time.
+ */
+ bool mNativeInited;
+};
+
+#if defined(__OBJC__)
+ void FireNativeEvent(mozAccessible* aNativeAcc, uint32_t aEventType);
+#else
+ void FireNativeEvent(id aNativeAcc, uint32_t aEventType);
+#endif
+
+Class GetTypeFromRole(roles::Role aRole);
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/AccessibleWrap.mm b/accessible/mac/AccessibleWrap.mm
new file mode 100644
index 000000000..65f2e1db4
--- /dev/null
+++ b/accessible/mac/AccessibleWrap.mm
@@ -0,0 +1,256 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "DocAccessible.h"
+#include "nsObjCExceptions.h"
+
+#include "Accessible-inl.h"
+#include "nsAccUtils.h"
+#include "Role.h"
+
+#import "mozAccessible.h"
+#import "mozActionElements.h"
+#import "mozHTMLAccessible.h"
+#import "mozTableAccessible.h"
+#import "mozTextAccessible.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+AccessibleWrap::
+ AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) :
+ Accessible(aContent, aDoc), mNativeObject(nil),
+ mNativeInited(false)
+{
+}
+
+AccessibleWrap::~AccessibleWrap()
+{
+}
+
+mozAccessible*
+AccessibleWrap::GetNativeObject()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!mNativeInited && !mNativeObject && !IsDefunct() && !AncestorIsFlat()) {
+ uintptr_t accWrap = reinterpret_cast<uintptr_t>(this);
+ mNativeObject = [[GetNativeType() alloc] initWithAccessible:accWrap];
+ }
+
+ mNativeInited = true;
+
+ return mNativeObject;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+void
+AccessibleWrap::GetNativeInterface(void** aOutInterface)
+{
+ *aOutInterface = static_cast<void*>(GetNativeObject());
+}
+
+// overridden in subclasses to create the right kind of object. by default we create a generic
+// 'mozAccessible' node.
+Class
+AccessibleWrap::GetNativeType ()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (IsXULTabpanels())
+ return [mozPaneAccessible class];
+
+ if (IsTable())
+ return [mozTableAccessible class];
+
+ if (IsTableRow())
+ return [mozTableRowAccessible class];
+
+ if (IsTableCell())
+ return [mozTableCellAccessible class];
+
+ return GetTypeFromRole(Role());
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// this method is very important. it is fired when an accessible object "dies". after this point
+// the object might still be around (because some 3rd party still has a ref to it), but it is
+// in fact 'dead'.
+void
+AccessibleWrap::Shutdown ()
+{
+ // this ensure we will not try to re-create the native object.
+ mNativeInited = true;
+
+ // we really intend to access the member directly.
+ if (mNativeObject) {
+ [mNativeObject expire];
+ [mNativeObject release];
+ mNativeObject = nil;
+ }
+
+ Accessible::Shutdown();
+}
+
+nsresult
+AccessibleWrap::HandleAccEvent(AccEvent* aEvent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsresult rv = Accessible::HandleAccEvent(aEvent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (IPCAccessibilityActive()) {
+ return NS_OK;
+ }
+
+ uint32_t eventType = aEvent->GetEventType();
+
+ // ignore everything but focus-changed, value-changed, caret, selection
+ // and document load complete events for now.
+ if (eventType != nsIAccessibleEvent::EVENT_FOCUS &&
+ eventType != nsIAccessibleEvent::EVENT_VALUE_CHANGE &&
+ eventType != nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE &&
+ eventType != nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED &&
+ eventType != nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED &&
+ eventType != nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE)
+ return NS_OK;
+
+ Accessible* accessible = aEvent->GetAccessible();
+ NS_ENSURE_STATE(accessible);
+
+ mozAccessible *nativeAcc = nil;
+ accessible->GetNativeInterface((void**)&nativeAcc);
+ if (!nativeAcc)
+ return NS_ERROR_FAILURE;
+
+ FireNativeEvent(nativeAcc, eventType);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+bool
+AccessibleWrap::InsertChildAt(uint32_t aIdx, Accessible* aAccessible)
+{
+ bool inserted = Accessible::InsertChildAt(aIdx, aAccessible);
+ if (inserted && mNativeObject)
+ [mNativeObject appendChild:aAccessible];
+
+ return inserted;
+}
+
+bool
+AccessibleWrap::RemoveChild(Accessible* aAccessible)
+{
+ bool removed = Accessible::RemoveChild(aAccessible);
+
+ if (removed && mNativeObject)
+ [mNativeObject invalidateChildren];
+
+ return removed;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AccessibleWrap protected
+
+bool
+AccessibleWrap::AncestorIsFlat()
+{
+ // We don't create a native object if we're child of a "flat" accessible;
+ // for example, on OS X buttons shouldn't have any children, because that
+ // makes the OS confused.
+ //
+ // To maintain a scripting environment where the XPCOM accessible hierarchy
+ // look the same on all platforms, we still let the C++ objects be created
+ // though.
+
+ Accessible* parent = Parent();
+ while (parent) {
+ if (nsAccUtils::MustPrune(parent))
+ return true;
+
+ parent = parent->Parent();
+ }
+ // no parent was flat
+ return false;
+}
+
+void
+a11y::FireNativeEvent(mozAccessible* aNativeAcc, uint32_t aEventType)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ switch (aEventType) {
+ case nsIAccessibleEvent::EVENT_FOCUS:
+ [aNativeAcc didReceiveFocus];
+ break;
+ case nsIAccessibleEvent::EVENT_VALUE_CHANGE:
+ case nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE:
+ [aNativeAcc valueDidChange];
+ break;
+ case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED:
+ case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED:
+ [aNativeAcc selectedTextDidChange];
+ break;
+ case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE:
+ [aNativeAcc documentLoadComplete];
+ break;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+Class
+a11y::GetTypeFromRole(roles::Role aRole)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ switch (aRole) {
+ case roles::COMBOBOX:
+ case roles::PUSHBUTTON:
+ case roles::SPLITBUTTON:
+ case roles::TOGGLE_BUTTON:
+ {
+ return [mozButtonAccessible class];
+ }
+
+ case roles::PAGETAB:
+ return [mozButtonAccessible class];
+
+ case roles::CHECKBUTTON:
+ return [mozCheckboxAccessible class];
+
+ case roles::HEADING:
+ return [mozHeadingAccessible class];
+
+ case roles::PAGETABLIST:
+ return [mozTabsAccessible class];
+
+ case roles::ENTRY:
+ case roles::STATICTEXT:
+ case roles::CAPTION:
+ case roles::ACCEL_LABEL:
+ case roles::PASSWORD_TEXT:
+ // normal textfield (static or editable)
+ return [mozTextAccessible class];
+
+ case roles::TEXT_LEAF:
+ return [mozTextLeafAccessible class];
+
+ case roles::LINK:
+ return [mozLinkAccessible class];
+
+ default:
+ return [mozAccessible class];
+ }
+
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
diff --git a/accessible/mac/ApplicationAccessibleWrap.h b/accessible/mac/ApplicationAccessibleWrap.h
new file mode 100644
index 000000000..9343c29dd
--- /dev/null
+++ b/accessible/mac/ApplicationAccessibleWrap.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* 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 {
+
+typedef ApplicationAccessible ApplicationAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/mac/DocAccessibleWrap.h b/accessible/mac/DocAccessibleWrap.h
new file mode 100644
index 000000000..3e80a0d33
--- /dev/null
+++ b/accessible/mac/DocAccessibleWrap.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_DocAccessibleWrap_h__
+#define mozilla_a11y_DocAccessibleWrap_h__
+
+#include "DocAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+class DocAccessibleWrap : public DocAccessible
+{
+public:
+ DocAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell);
+ virtual ~DocAccessibleWrap();
+
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/DocAccessibleWrap.mm b/accessible/mac/DocAccessibleWrap.mm
new file mode 100644
index 000000000..8a513f485
--- /dev/null
+++ b/accessible/mac/DocAccessibleWrap.mm
@@ -0,0 +1,21 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * 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"
+
+#import "mozAccessible.h"
+
+using namespace mozilla::a11y;
+
+DocAccessibleWrap::
+ DocAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell) :
+ DocAccessible(aDocument, aPresShell)
+{
+}
+
+DocAccessibleWrap::~DocAccessibleWrap()
+{
+}
+
diff --git a/accessible/mac/HTMLTableAccessibleWrap.h b/accessible/mac/HTMLTableAccessibleWrap.h
new file mode 100644
index 000000000..4f158e241
--- /dev/null
+++ b/accessible/mac/HTMLTableAccessibleWrap.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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_HTMLTableAccessibleWrap_h__
+#define mozilla_a11y_HTMLTableAccessibleWrap_h__
+
+#include "HTMLTableAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class HTMLTableAccessible HTMLTableAccessibleWrap;
+typedef class HTMLTableCellAccessible HTMLTableCellAccessibleWrap;
+typedef class HTMLTableHeaderCellAccessible HTMLTableHeaderCellAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/mac/HyperTextAccessibleWrap.h b/accessible/mac/HyperTextAccessibleWrap.h
new file mode 100644
index 000000000..fb335ef0f
--- /dev/null
+++ b/accessible/mac/HyperTextAccessibleWrap.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_HyperTextAccessibleWrap_h__
+#define mozilla_a11y_HyperTextAccessibleWrap_h__
+
+#include "HyperTextAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class HyperTextAccessible HyperTextAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/mac/ImageAccessibleWrap.h b/accessible/mac/ImageAccessibleWrap.h
new file mode 100644
index 000000000..069efb651
--- /dev/null
+++ b/accessible/mac/ImageAccessibleWrap.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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_ImageAccessibleWrap_h__
+#define mozilla_a11y_ImageAccessibleWrap_h__
+
+#include "ImageAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class ImageAccessible ImageAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/mac/MacUtils.h b/accessible/mac/MacUtils.h
new file mode 100644
index 000000000..69455ccb6
--- /dev/null
+++ b/accessible/mac/MacUtils.h
@@ -0,0 +1,27 @@
+/* -*- Mode: Objective-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 _MacUtils_H_
+#define _MacUtils_H_
+
+@class NSString;
+class nsString;
+
+namespace mozilla {
+namespace a11y {
+namespace utils {
+
+/**
+ * Get a localized string from the string bundle.
+ * Return nil if not found.
+ */
+NSString* LocalizedString(const nsString& aString);
+
+}
+}
+}
+
+#endif
diff --git a/accessible/mac/MacUtils.mm b/accessible/mac/MacUtils.mm
new file mode 100644
index 000000000..f8e06113d
--- /dev/null
+++ b/accessible/mac/MacUtils.mm
@@ -0,0 +1,33 @@
+/* -*- Mode: Objective-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/. */
+
+#import "MacUtils.h"
+
+#include "Accessible.h"
+
+#include "nsCocoaUtils.h"
+
+namespace mozilla {
+namespace a11y {
+namespace utils {
+
+/**
+ * Get a localized string from the a11y string bundle.
+ * Return nil if not found.
+ */
+NSString*
+LocalizedString(const nsString& aString)
+{
+ nsString text;
+
+ Accessible::TranslateString(aString, text);
+
+ return text.IsEmpty() ? nil : nsCocoaUtils::ToNSString(text);
+}
+
+}
+}
+}
diff --git a/accessible/mac/Platform.mm b/accessible/mac/Platform.mm
new file mode 100644
index 000000000..d7696e1a6
--- /dev/null
+++ b/accessible/mac/Platform.mm
@@ -0,0 +1,175 @@
+/* -*- Mode: Objective-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/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "Platform.h"
+#include "ProxyAccessible.h"
+#include "DocAccessibleParent.h"
+#include "mozTableAccessible.h"
+
+#include "nsAppShell.h"
+
+namespace mozilla {
+namespace a11y {
+
+// Mac a11y whitelisting
+static bool sA11yShouldBeEnabled = false;
+
+bool
+ShouldA11yBeEnabled()
+{
+ EPlatformDisabledState disabledState = PlatformDisabledState();
+ return (disabledState == ePlatformIsForceEnabled) || ((disabledState == ePlatformIsEnabled) && sA11yShouldBeEnabled);
+}
+
+void
+PlatformInit()
+{
+}
+
+void
+PlatformShutdown()
+{
+}
+
+void
+ProxyCreated(ProxyAccessible* aProxy, uint32_t)
+{
+ // Pass in dummy state for now as retrieving proxy state requires IPC.
+ // Note that we can use ProxyAccessible::IsTable* functions here because they
+ // do not use IPC calls but that might change after bug 1210477.
+ Class type;
+ if (aProxy->IsTable())
+ type = [mozTableAccessible class];
+ else if (aProxy->IsTableRow())
+ type = [mozTableRowAccessible class];
+ else if (aProxy->IsTableCell())
+ type = [mozTableCellAccessible class];
+ else
+ type = GetTypeFromRole(aProxy->Role());
+
+ uintptr_t accWrap = reinterpret_cast<uintptr_t>(aProxy) | IS_PROXY;
+ mozAccessible* mozWrapper = [[type alloc] initWithAccessible:accWrap];
+ aProxy->SetWrapper(reinterpret_cast<uintptr_t>(mozWrapper));
+
+ mozAccessible* nativeParent = nullptr;
+ if (aProxy->IsDoc() && aProxy->AsDoc()->IsTopLevel()) {
+ // If proxy is top level, the parent we need to invalidate the children of
+ // will be a non-remote accessible.
+ Accessible* outerDoc = aProxy->OuterDocOfRemoteBrowser();
+ if (outerDoc) {
+ nativeParent = GetNativeFromGeckoAccessible(outerDoc);
+ }
+ } else {
+ // Non-top level proxies need proxy parents' children invalidated.
+ ProxyAccessible* parent = aProxy->Parent();
+ nativeParent = GetNativeFromProxy(parent);
+ NS_ASSERTION(parent, "a non-top-level proxy is missing a parent?");
+ }
+
+ if (nativeParent) {
+ [nativeParent invalidateChildren];
+ }
+}
+
+void
+ProxyDestroyed(ProxyAccessible* aProxy)
+{
+ mozAccessible* nativeParent = nil;
+ if (aProxy->IsDoc() && aProxy->AsDoc()->IsTopLevel()) {
+ // Invalidate native parent in parent process's children on proxy destruction
+ Accessible* outerDoc = aProxy->OuterDocOfRemoteBrowser();
+ if (outerDoc) {
+ nativeParent = GetNativeFromGeckoAccessible(outerDoc);
+ }
+ } else {
+ if (!aProxy->Document()->IsShutdown()) {
+ // Only do if the document has not been shut down, else parent will return
+ // garbage since we don't shut down children from top down.
+ ProxyAccessible* parent = aProxy->Parent();
+ // Invalidate proxy parent's children.
+ if (parent) {
+ nativeParent = GetNativeFromProxy(parent);
+ }
+ }
+ }
+
+ mozAccessible* wrapper = GetNativeFromProxy(aProxy);
+ [wrapper expire];
+ [wrapper release];
+ aProxy->SetWrapper(0);
+
+ if (nativeParent) {
+ [nativeParent invalidateChildren];
+ }
+}
+
+void
+ProxyEvent(ProxyAccessible* aProxy, uint32_t aEventType)
+{
+ // ignore everything but focus-changed, value-changed, caret and selection
+ // events for now.
+ if (aEventType != nsIAccessibleEvent::EVENT_FOCUS &&
+ aEventType != nsIAccessibleEvent::EVENT_VALUE_CHANGE &&
+ aEventType != nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE &&
+ aEventType != nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED &&
+ aEventType != nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED)
+ return;
+
+ mozAccessible* wrapper = GetNativeFromProxy(aProxy);
+ if (wrapper)
+ FireNativeEvent(wrapper, aEventType);
+}
+
+void
+ProxyStateChangeEvent(ProxyAccessible* aProxy, uint64_t, bool)
+{
+ // mac doesn't care about state change events
+}
+
+void
+ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset)
+{
+ mozAccessible* wrapper = GetNativeFromProxy(aTarget);
+ if (wrapper)
+ [wrapper selectedTextDidChange];
+}
+
+void
+ProxyTextChangeEvent(ProxyAccessible*, const nsString&, int32_t, uint32_t,
+ bool, bool)
+{
+}
+
+void
+ProxyShowHideEvent(ProxyAccessible*, ProxyAccessible*, bool, bool)
+{
+}
+
+void
+ProxySelectionEvent(ProxyAccessible*, ProxyAccessible*, uint32_t)
+{
+}
+} // namespace a11y
+} // namespace mozilla
+
+@interface GeckoNSApplication(a11y)
+-(void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute;
+@end
+
+@implementation GeckoNSApplication(a11y)
+
+-(void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute
+{
+ if ([attribute isEqualToString:@"AXEnhancedUserInterface"])
+ mozilla::a11y::sA11yShouldBeEnabled = ([value intValue] == 1);
+
+ return [super accessibilitySetValue:value forAttribute:attribute];
+}
+
+@end
+
diff --git a/accessible/mac/RootAccessibleWrap.h b/accessible/mac/RootAccessibleWrap.h
new file mode 100644
index 000000000..aa53e06ac
--- /dev/null
+++ b/accessible/mac/RootAccessibleWrap.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_RootAccessibleWrap_h__
+#define mozilla_a11y_RootAccessibleWrap_h__
+
+#include "RootAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+class RootAccessibleWrap : public RootAccessible
+{
+public:
+ RootAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell);
+ virtual ~RootAccessibleWrap();
+
+ Class GetNativeType ();
+
+ // let's our native accessible get in touch with the
+ // native cocoa view that is our accessible parent.
+ void GetNativeWidget (void **aOutView);
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/RootAccessibleWrap.mm b/accessible/mac/RootAccessibleWrap.mm
new file mode 100644
index 000000000..037545cce
--- /dev/null
+++ b/accessible/mac/RootAccessibleWrap.mm
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "mozDocAccessible.h"
+
+#include "nsCOMPtr.h"
+#include "nsObjCExceptions.h"
+#include "nsIFrame.h"
+#include "nsView.h"
+#include "nsIWidget.h"
+
+using namespace mozilla::a11y;
+
+RootAccessibleWrap::
+ RootAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell) :
+ RootAccessible(aDocument, aPresShell)
+{
+}
+
+RootAccessibleWrap::~RootAccessibleWrap()
+{
+}
+
+Class
+RootAccessibleWrap::GetNativeType()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [mozRootAccessible class];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+void
+RootAccessibleWrap::GetNativeWidget(void** aOutView)
+{
+ nsIFrame *frame = GetFrame();
+ if (frame) {
+ nsView *view = frame->GetView();
+ if (view) {
+ nsIWidget *widget = view->GetWidget();
+ if (widget) {
+ *aOutView = (void**)widget->GetNativeData (NS_NATIVE_WIDGET);
+ NS_ASSERTION (*aOutView,
+ "Couldn't get the native NSView parent we need to connect the accessibility hierarchy!");
+ }
+ }
+ }
+}
diff --git a/accessible/mac/TextLeafAccessibleWrap.h b/accessible/mac/TextLeafAccessibleWrap.h
new file mode 100644
index 000000000..d07b9defe
--- /dev/null
+++ b/accessible/mac/TextLeafAccessibleWrap.h
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_TextLeafAccessibleWrap_h__
+#define mozilla_a11y_TextLeafAccessibleWrap_h__
+
+#include "TextLeafAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class TextLeafAccessible TextLeafAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/XULListboxAccessibleWrap.h b/accessible/mac/XULListboxAccessibleWrap.h
new file mode 100644
index 000000000..f7dc6cc54
--- /dev/null
+++ b/accessible/mac/XULListboxAccessibleWrap.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_XULListboxAccessibleWrap_h__
+#define mozilla_a11y_XULListboxAccessibleWrap_h__
+
+#include "XULListboxAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class XULListboxAccessible XULListboxAccessibleWrap;
+typedef class XULListCellAccessible XULListCellAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/XULMenuAccessibleWrap.h b/accessible/mac/XULMenuAccessibleWrap.h
new file mode 100644
index 000000000..6efcf007e
--- /dev/null
+++ b/accessible/mac/XULMenuAccessibleWrap.h
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_XULMenuAccessibleWrap_h__
+#define mozilla_a11y_XULMenuAccessibleWrap_h__
+
+#include "XULMenuAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class XULMenuitemAccessible XULMenuitemAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/XULTreeGridAccessibleWrap.h b/accessible/mac/XULTreeGridAccessibleWrap.h
new file mode 100644
index 000000000..b3631e9ad
--- /dev/null
+++ b/accessible/mac/XULTreeGridAccessibleWrap.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_XULTreeGridAccessibleWrap_h__
+#define mozilla_a11y_XULTreeGridAccessibleWrap_h__
+
+#include "XULTreeGridAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class XULTreeGridAccessible XULTreeGridAccessibleWrap;
+typedef class XULTreeGridCellAccessible XULTreeGridCellAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/moz.build b/accessible/mac/moz.build
new file mode 100644
index 000000000..a8f07c48b
--- /dev/null
+++ b/accessible/mac/moz.build
@@ -0,0 +1,45 @@
+# -*- 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 += [
+ 'mozAccessibleProtocol.h',
+]
+
+EXPORTS.mozilla.a11y += [
+ 'AccessibleWrap.h',
+ 'HyperTextAccessibleWrap.h',
+]
+
+UNIFIED_SOURCES += [
+ 'AccessibleWrap.mm',
+ 'DocAccessibleWrap.mm',
+ 'MacUtils.mm',
+ 'mozAccessible.mm',
+ 'mozActionElements.mm',
+ 'mozDocAccessible.mm',
+ 'mozHTMLAccessible.mm',
+ 'mozTableAccessible.mm',
+ 'mozTextAccessible.mm',
+ 'Platform.mm',
+ 'RootAccessibleWrap.mm',
+]
+
+LOCAL_INCLUDES += [
+ '/accessible/base',
+ '/accessible/generic',
+ '/accessible/html',
+ '/accessible/ipc',
+ '/accessible/ipc/other',
+ '/accessible/xul',
+ '/layout/generic',
+ '/layout/xul',
+ '/widget',
+ '/widget/cocoa',
+]
+
+FINAL_LIBRARY = 'xul'
+
+include('/ipc/chromium/chromium-config.mozbuild')
diff --git a/accessible/mac/mozAccessible.h b/accessible/mac/mozAccessible.h
new file mode 100644
index 000000000..6d7db3fe9
--- /dev/null
+++ b/accessible/mac/mozAccessible.h
@@ -0,0 +1,181 @@
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "ProxyAccessible.h"
+
+#import <Cocoa/Cocoa.h>
+
+#import "mozAccessibleProtocol.h"
+
+@class mozRootAccessible;
+
+/**
+ * All mozAccessibles are either abstract objects (that correspond to XUL
+ * widgets, HTML frames, etc) or are attached to a certain view; for example
+ * a document view. When we hand an object off to an AT, we always want
+ * to give it the represented view, in the latter case.
+ */
+
+namespace mozilla {
+namespace a11y {
+
+inline id <mozAccessible>
+GetObjectOrRepresentedView(id <mozAccessible> aObject)
+{
+ return [aObject hasRepresentedView] ? [aObject representedView] : aObject;
+}
+
+inline mozAccessible*
+GetNativeFromGeckoAccessible(Accessible* aAccessible)
+{
+ mozAccessible* native = nil;
+ aAccessible->GetNativeInterface((void**)&native);
+ return native;
+}
+
+inline mozAccessible*
+GetNativeFromProxy(const ProxyAccessible* aProxy)
+{
+ return reinterpret_cast<mozAccessible*>(aProxy->GetWrapper());
+}
+
+} // a11y
+} // mozilla
+
+// This is OR'd with the Accessible owner to indicate the wrap-ee is a proxy.
+static const uintptr_t IS_PROXY = 1;
+
+@interface mozAccessible : NSObject <mozAccessible>
+{
+ /**
+ * Weak reference; it owns us.
+ */
+ uintptr_t mGeckoAccessible;
+
+ /**
+ * Strong ref to array of children
+ */
+ NSMutableArray* mChildren;
+
+ /**
+ * Weak reference to the parent
+ */
+ mozAccessible* mParent;
+
+ /**
+ * The role of our gecko accessible.
+ */
+ mozilla::a11y::role mRole;
+}
+
+// return the Accessible for this mozAccessible if it exists.
+- (mozilla::a11y::AccessibleWrap*)getGeckoAccessible;
+
+// return the ProxyAccessible for this mozAccessible if it exists.
+- (mozilla::a11y::ProxyAccessible*)getProxyAccessible;
+
+// inits with the gecko owner.
+- (id)initWithAccessible:(uintptr_t)aGeckoObj;
+
+// our accessible parent (AXParent)
+- (id <mozAccessible>)parent;
+
+// a lazy cache of our accessible children (AXChildren). updated
+- (NSArray*)children;
+
+// returns the size of this accessible.
+- (NSValue*)size;
+
+// returns the position, in cocoa coordinates.
+- (NSValue*)position;
+
+// can be overridden to report another role name.
+- (NSString*)role;
+
+// a subrole is a more specialized variant of the role. for example,
+// the role might be "textfield", while the subrole is "password textfield".
+- (NSString*)subrole;
+
+// Return the role description, as there are a few exceptions.
+- (NSString*)roleDescription;
+
+// returns the native window we're inside.
+- (NSWindow*)window;
+
+// the value of this element.
+- (id)value;
+
+// name that is associated with this accessible (for buttons, etc)
+- (NSString*)title;
+
+// the accessible description (help text) of this particular instance.
+- (NSString*)help;
+
+- (BOOL)isEnabled;
+
+// information about focus.
+- (BOOL)isFocused;
+- (BOOL)canBeFocused;
+
+// returns NO if for some reason we were unable to focus the element.
+- (BOOL)focus;
+
+// notifications sent out to listening accessible providers.
+- (void)didReceiveFocus;
+- (void)valueDidChange;
+- (void)selectedTextDidChange;
+- (void)documentLoadComplete;
+
+// internal method to retrieve a child at a given index.
+- (id)childAt:(uint32_t)i;
+
+#pragma mark -
+
+// invalidates and removes all our children from our cached array.
+- (void)invalidateChildren;
+
+/**
+ * Append a child if they are already cached.
+ */
+- (void)appendChild:(mozilla::a11y::Accessible*)aAccessible;
+
+// makes ourselves "expired". after this point, we might be around if someone
+// has retained us (e.g., a third-party), but we really contain no information.
+- (void)expire;
+- (BOOL)isExpired;
+
+#ifdef DEBUG
+- (void)printHierarchy;
+- (void)printHierarchyWithLevel:(unsigned)numSpaces;
+
+- (void)sanityCheckChildren;
+- (void)sanityCheckChildren:(NSArray*)theChildren;
+#endif
+
+// ---- NSAccessibility methods ---- //
+
+// whether to skip this element when traversing the accessibility
+// hierarchy.
+- (BOOL)accessibilityIsIgnored;
+
+// called by third-parties to determine the deepest child element under the mouse
+- (id)accessibilityHitTest:(NSPoint)point;
+
+// returns the deepest unignored focused accessible element
+- (id)accessibilityFocusedUIElement;
+
+// a mozAccessible needs to at least provide links to its parent and
+// children.
+- (NSArray*)accessibilityAttributeNames;
+
+// value for the specified attribute
+- (id)accessibilityAttributeValue:(NSString*)attribute;
+
+- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute;
+- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute;
+
+@end
+
diff --git a/accessible/mac/mozAccessible.mm b/accessible/mac/mozAccessible.mm
new file mode 100644
index 000000000..e1cdba694
--- /dev/null
+++ b/accessible/mac/mozAccessible.mm
@@ -0,0 +1,1236 @@
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#import "mozAccessible.h"
+
+#import "MacUtils.h"
+#import "mozView.h"
+
+#include "Accessible-inl.h"
+#include "nsAccUtils.h"
+#include "nsIAccessibleRelation.h"
+#include "nsIAccessibleEditableText.h"
+#include "nsIPersistentProperties2.h"
+#include "Relation.h"
+#include "Role.h"
+#include "RootAccessible.h"
+#include "TableAccessible.h"
+#include "TableCellAccessible.h"
+#include "mozilla/a11y/PDocAccessible.h"
+#include "OuterDocAccessible.h"
+
+#include "mozilla/Services.h"
+#include "nsRect.h"
+#include "nsCocoaUtils.h"
+#include "nsCoord.h"
+#include "nsObjCExceptions.h"
+#include "nsWhitespaceTokenizer.h"
+#include <prdtoa.h>
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+#define NSAccessibilityMathRootRadicandAttribute @"AXMathRootRadicand"
+#define NSAccessibilityMathRootIndexAttribute @"AXMathRootIndex"
+#define NSAccessibilityMathFractionNumeratorAttribute @"AXMathFractionNumerator"
+#define NSAccessibilityMathFractionDenominatorAttribute @"AXMathFractionDenominator"
+#define NSAccessibilityMathBaseAttribute @"AXMathBase"
+#define NSAccessibilityMathSubscriptAttribute @"AXMathSubscript"
+#define NSAccessibilityMathSuperscriptAttribute @"AXMathSuperscript"
+#define NSAccessibilityMathUnderAttribute @"AXMathUnder"
+#define NSAccessibilityMathOverAttribute @"AXMathOver"
+#define NSAccessibilityMathLineThicknessAttribute @"AXMathLineThickness"
+// XXX WebKit also defines the following attributes.
+// See bugs 1176970 and 1176983.
+// - NSAccessibilityMathFencedOpenAttribute @"AXMathFencedOpen"
+// - NSAccessibilityMathFencedCloseAttribute @"AXMathFencedClose"
+// - NSAccessibilityMathPrescriptsAttribute @"AXMathPrescripts"
+// - NSAccessibilityMathPostscriptsAttribute @"AXMathPostscripts"
+
+// convert an array of Gecko accessibles to an NSArray of native accessibles
+static inline NSMutableArray*
+ConvertToNSArray(nsTArray<Accessible*>& aArray)
+{
+ NSMutableArray* nativeArray = [[NSMutableArray alloc] init];
+
+ // iterate through the list, and get each native accessible.
+ size_t totalCount = aArray.Length();
+ for (size_t i = 0; i < totalCount; i++) {
+ Accessible* curAccessible = aArray.ElementAt(i);
+ mozAccessible* curNative = GetNativeFromGeckoAccessible(curAccessible);
+ if (curNative)
+ [nativeArray addObject:GetObjectOrRepresentedView(curNative)];
+ }
+
+ return nativeArray;
+}
+
+// convert an array of Gecko proxy accessibles to an NSArray of native accessibles
+static inline NSMutableArray*
+ConvertToNSArray(nsTArray<ProxyAccessible*>& aArray)
+{
+ NSMutableArray* nativeArray = [[NSMutableArray alloc] init];
+
+ // iterate through the list, and get each native accessible.
+ size_t totalCount = aArray.Length();
+ for (size_t i = 0; i < totalCount; i++) {
+ ProxyAccessible* curAccessible = aArray.ElementAt(i);
+ mozAccessible* curNative = GetNativeFromProxy(curAccessible);
+ if (curNative)
+ [nativeArray addObject:GetObjectOrRepresentedView(curNative)];
+ }
+
+ return nativeArray;
+}
+
+#pragma mark -
+
+@implementation mozAccessible
+
+- (id)initWithAccessible:(uintptr_t)aGeckoAccessible
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ((self = [super init])) {
+ mGeckoAccessible = aGeckoAccessible;
+ if (aGeckoAccessible & IS_PROXY)
+ mRole = [self getProxyAccessible]->Role();
+ else
+ mRole = [self getGeckoAccessible]->Role();
+ }
+
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mChildren release];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (mozilla::a11y::AccessibleWrap*)getGeckoAccessible
+{
+ // Check if mGeckoAccessible points at a proxy
+ if (mGeckoAccessible & IS_PROXY)
+ return nil;
+
+ return reinterpret_cast<AccessibleWrap*>(mGeckoAccessible);
+}
+
+- (mozilla::a11y::ProxyAccessible*)getProxyAccessible
+{
+ // Check if mGeckoAccessible points at a proxy
+ if (!(mGeckoAccessible & IS_PROXY))
+ return nil;
+
+ return reinterpret_cast<ProxyAccessible*>(mGeckoAccessible & ~IS_PROXY);
+}
+
+#pragma mark -
+
+- (BOOL)accessibilityIsIgnored
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ // unknown (either unimplemented, or irrelevant) elements are marked as ignored
+ // as well as expired elements.
+
+ bool noRole = [[self role] isEqualToString:NSAccessibilityUnknownRole];
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ return (noRole && !(accWrap->InteractiveState() & states::FOCUSABLE));
+
+ if (ProxyAccessible* proxy = [self getProxyAccessible])
+ return (noRole && !(proxy->State() & states::FOCUSABLE));
+
+ return true;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+- (NSArray*)additionalAccessibilityAttributeNames
+{
+ NSMutableArray* additional = [NSMutableArray array];
+ switch (mRole) {
+ case roles::MATHML_ROOT:
+ [additional addObject:NSAccessibilityMathRootIndexAttribute];
+ [additional addObject:NSAccessibilityMathRootRadicandAttribute];
+ break;
+ case roles::MATHML_SQUARE_ROOT:
+ [additional addObject:NSAccessibilityMathRootRadicandAttribute];
+ break;
+ case roles::MATHML_FRACTION:
+ [additional addObject:NSAccessibilityMathFractionNumeratorAttribute];
+ [additional addObject:NSAccessibilityMathFractionDenominatorAttribute];
+ [additional addObject:NSAccessibilityMathLineThicknessAttribute];
+ break;
+ case roles::MATHML_SUB:
+ case roles::MATHML_SUP:
+ case roles::MATHML_SUB_SUP:
+ [additional addObject:NSAccessibilityMathBaseAttribute];
+ [additional addObject:NSAccessibilityMathSubscriptAttribute];
+ [additional addObject:NSAccessibilityMathSuperscriptAttribute];
+ break;
+ case roles::MATHML_UNDER:
+ case roles::MATHML_OVER:
+ case roles::MATHML_UNDER_OVER:
+ [additional addObject:NSAccessibilityMathBaseAttribute];
+ [additional addObject:NSAccessibilityMathUnderAttribute];
+ [additional addObject:NSAccessibilityMathOverAttribute];
+ break;
+ // XXX bug 1176983
+ // roles::MATHML_MULTISCRIPTS should also have the following attributes:
+ // - NSAccessibilityMathPrescriptsAttribute
+ // - NSAccessibilityMathPostscriptsAttribute
+ // XXX bug 1176970
+ // roles::MATHML_FENCED should also have the following attributes:
+ // - NSAccessibilityMathFencedOpenAttribute
+ // - NSAccessibilityMathFencedCloseAttribute
+ default:
+ break;
+ }
+
+ return additional;
+}
+
+- (NSArray*)accessibilityAttributeNames
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ // if we're expired, we don't support any attributes.
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ if (!accWrap && !proxy)
+ return [NSArray array];
+
+ static NSArray* generalAttributes = nil;
+
+ if (!generalAttributes) {
+ // standard attributes that are shared and supported by all generic elements.
+ generalAttributes = [[NSArray alloc] initWithObjects: NSAccessibilityChildrenAttribute,
+ NSAccessibilityParentAttribute,
+ NSAccessibilityRoleAttribute,
+ NSAccessibilityTitleAttribute,
+ NSAccessibilityValueAttribute,
+ NSAccessibilitySubroleAttribute,
+ NSAccessibilityRoleDescriptionAttribute,
+ NSAccessibilityPositionAttribute,
+ NSAccessibilityEnabledAttribute,
+ NSAccessibilitySizeAttribute,
+ NSAccessibilityWindowAttribute,
+ NSAccessibilityFocusedAttribute,
+ NSAccessibilityHelpAttribute,
+ NSAccessibilityTitleUIElementAttribute,
+ NSAccessibilityTopLevelUIElementAttribute,
+#if DEBUG
+ @"AXMozDescription",
+#endif
+ nil];
+ }
+
+ NSArray* objectAttributes = generalAttributes;
+
+ NSArray* additionalAttributes = [self additionalAccessibilityAttributeNames];
+ if ([additionalAttributes count])
+ objectAttributes = [objectAttributes arrayByAddingObjectsFromArray:additionalAttributes];
+
+ return objectAttributes;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)childAt:(uint32_t)i
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ Accessible* child = accWrap->GetChildAt(i);
+ return child ? GetNativeFromGeckoAccessible(child) : nil;
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ ProxyAccessible* child = proxy->ChildAt(i);
+ return child ? GetNativeFromProxy(child) : nil;
+ }
+
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)accessibilityAttributeValue:(NSString*)attribute
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ if (!accWrap && !proxy)
+ return nil;
+
+#if DEBUG
+ if ([attribute isEqualToString:@"AXMozDescription"])
+ return [NSString stringWithFormat:@"role = %u native = %@", mRole, [self class]];
+#endif
+
+ if ([attribute isEqualToString:NSAccessibilityChildrenAttribute])
+ return [self children];
+ if ([attribute isEqualToString:NSAccessibilityParentAttribute])
+ return [self parent];
+
+#ifdef DEBUG_hakan
+ NSLog (@"(%@ responding to attr %@)", self, attribute);
+#endif
+
+ if ([attribute isEqualToString:NSAccessibilityRoleAttribute])
+ return [self role];
+ if ([attribute isEqualToString:NSAccessibilityPositionAttribute])
+ return [self position];
+ if ([attribute isEqualToString:NSAccessibilitySubroleAttribute])
+ return [self subrole];
+ if ([attribute isEqualToString:NSAccessibilityEnabledAttribute])
+ return [NSNumber numberWithBool:[self isEnabled]];
+ if ([attribute isEqualToString:NSAccessibilityValueAttribute])
+ return [self value];
+ if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute])
+ return [self roleDescription];
+ if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
+ return [NSNumber numberWithBool:[self isFocused]];
+ if ([attribute isEqualToString:NSAccessibilitySizeAttribute])
+ return [self size];
+ if ([attribute isEqualToString:NSAccessibilityWindowAttribute])
+ return [self window];
+ if ([attribute isEqualToString:NSAccessibilityTopLevelUIElementAttribute])
+ return [self window];
+ if ([attribute isEqualToString:NSAccessibilityTitleAttribute])
+ return [self title];
+ if ([attribute isEqualToString:NSAccessibilityTitleUIElementAttribute]) {
+ if (accWrap) {
+ Relation rel = accWrap->RelationByType(RelationType::LABELLED_BY);
+ Accessible* tempAcc = rel.Next();
+ return tempAcc ? GetNativeFromGeckoAccessible(tempAcc) : nil;
+ }
+ nsTArray<ProxyAccessible*> rel = proxy->RelationByType(RelationType::LABELLED_BY);
+ ProxyAccessible* tempProxy = rel.SafeElementAt(0);
+ return tempProxy ? GetNativeFromProxy(tempProxy) : nil;
+ }
+ if ([attribute isEqualToString:NSAccessibilityHelpAttribute])
+ return [self help];
+
+ switch (mRole) {
+ case roles::MATHML_ROOT:
+ if ([attribute isEqualToString:NSAccessibilityMathRootRadicandAttribute])
+ return [self childAt:0];
+ if ([attribute isEqualToString:NSAccessibilityMathRootIndexAttribute])
+ return [self childAt:1];
+ break;
+ case roles::MATHML_SQUARE_ROOT:
+ if ([attribute isEqualToString:NSAccessibilityMathRootRadicandAttribute])
+ return [self childAt:0];
+ break;
+ case roles::MATHML_FRACTION:
+ if ([attribute isEqualToString:NSAccessibilityMathFractionNumeratorAttribute])
+ return [self childAt:0];
+ if ([attribute isEqualToString:NSAccessibilityMathFractionDenominatorAttribute])
+ return [self childAt:1];
+ if ([attribute isEqualToString:NSAccessibilityMathLineThicknessAttribute]) {
+ // WebKit sets line thickness to some logical value parsed in the
+ // renderer object of the <mfrac> element. It's not clear whether the
+ // exact value is relevant to assistive technologies. From a semantic
+ // point of view, the only important point is to distinguish between
+ // <mfrac> elements that have a fraction bar and those that do not.
+ // Per the MathML 3 spec, the latter happens iff the linethickness
+ // attribute is of the form [zero-float][optional-unit]. In that case we
+ // set line thickness to zero and in the other cases we set it to one.
+ nsAutoString thickness;
+ if (accWrap) {
+ nsCOMPtr<nsIPersistentProperties> attributes = accWrap->Attributes();
+ nsAccUtils::GetAccAttr(attributes, nsGkAtoms::linethickness_, thickness);
+ } else {
+ AutoTArray<Attribute, 10> attrs;
+ proxy->Attributes(&attrs);
+ for (size_t i = 0 ; i < attrs.Length() ; i++) {
+ if (attrs.ElementAt(i).Name() == "thickness") {
+ thickness = attrs.ElementAt(i).Value();
+ break;
+ }
+ }
+ }
+ double value = 1.0;
+ if (!thickness.IsEmpty())
+ value = PR_strtod(NS_LossyConvertUTF16toASCII(thickness).get(),
+ nullptr);
+ return [NSNumber numberWithInteger:(value ? 1 : 0)];
+ }
+ break;
+ case roles::MATHML_SUB:
+ if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
+ return [self childAt:0];
+ if ([attribute isEqualToString:NSAccessibilityMathSubscriptAttribute])
+ return [self childAt:1];
+#ifdef DEBUG
+ if ([attribute isEqualToString:NSAccessibilityMathSuperscriptAttribute])
+ return nil;
+#endif
+ break;
+ case roles::MATHML_SUP:
+ if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
+ return [self childAt:0];
+#ifdef DEBUG
+ if ([attribute isEqualToString:NSAccessibilityMathSubscriptAttribute])
+ return nil;
+#endif
+ if ([attribute isEqualToString:NSAccessibilityMathSuperscriptAttribute])
+ return [self childAt:1];
+ break;
+ case roles::MATHML_SUB_SUP:
+ if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
+ return [self childAt:0];
+ if ([attribute isEqualToString:NSAccessibilityMathSubscriptAttribute])
+ return [self childAt:1];
+ if ([attribute isEqualToString:NSAccessibilityMathSuperscriptAttribute])
+ return [self childAt:2];
+ break;
+ case roles::MATHML_UNDER:
+ if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
+ return [self childAt:0];
+ if ([attribute isEqualToString:NSAccessibilityMathUnderAttribute])
+ return [self childAt:1];
+#ifdef DEBUG
+ if ([attribute isEqualToString:NSAccessibilityMathOverAttribute])
+ return nil;
+#endif
+ break;
+ case roles::MATHML_OVER:
+ if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
+ return [self childAt:0];
+#ifdef DEBUG
+ if ([attribute isEqualToString:NSAccessibilityMathUnderAttribute])
+ return nil;
+#endif
+ if ([attribute isEqualToString:NSAccessibilityMathOverAttribute])
+ return [self childAt:1];
+ break;
+ case roles::MATHML_UNDER_OVER:
+ if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
+ return [self childAt:0];
+ if ([attribute isEqualToString:NSAccessibilityMathUnderAttribute])
+ return [self childAt:1];
+ if ([attribute isEqualToString:NSAccessibilityMathOverAttribute])
+ return [self childAt:2];
+ break;
+ // XXX bug 1176983
+ // roles::MATHML_MULTISCRIPTS should also have the following attributes:
+ // - NSAccessibilityMathPrescriptsAttribute
+ // - NSAccessibilityMathPostscriptsAttribute
+ // XXX bug 1176970
+ // roles::MATHML_FENCED should also have the following attributes:
+ // - NSAccessibilityMathFencedOpenAttribute
+ // - NSAccessibilityMathFencedCloseAttribute
+ default:
+ break;
+ }
+
+#ifdef DEBUG
+ NSLog (@"!!! %@ can't respond to attribute %@", self, attribute);
+#endif
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
+ return [self canBeFocused];
+
+ return NO;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+#ifdef DEBUG_hakan
+ NSLog (@"[%@] %@='%@'", self, attribute, value);
+#endif
+
+ // we only support focusing elements so far.
+ if ([attribute isEqualToString:NSAccessibilityFocusedAttribute] && [value boolValue])
+ [self focus];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (id)accessibilityHitTest:(NSPoint)point
+{
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ if (!accWrap && !proxy)
+ return nil;
+
+ // Convert the given screen-global point in the cocoa coordinate system (with
+ // origin in the bottom-left corner of the screen) into point in the Gecko
+ // coordinate system (with origin in a top-left screen point).
+ NSScreen* mainView = [[NSScreen screens] objectAtIndex:0];
+ NSPoint tmpPoint = NSMakePoint(point.x,
+ [mainView frame].size.height - point.y);
+ LayoutDeviceIntPoint geckoPoint = nsCocoaUtils::
+ CocoaPointsToDevPixels(tmpPoint, nsCocoaUtils::GetBackingScaleFactor(mainView));
+
+ mozAccessible* nativeChild = nil;
+ if (accWrap) {
+ Accessible* child = accWrap->ChildAtPoint(geckoPoint.x, geckoPoint.y,
+ Accessible::eDeepestChild);
+ if (child)
+ nativeChild = GetNativeFromGeckoAccessible(child);
+ } else if (proxy) {
+ ProxyAccessible* child = proxy->ChildAtPoint(geckoPoint.x, geckoPoint.y,
+ Accessible::eDeepestChild);
+ if (child)
+ nativeChild = GetNativeFromProxy(child);
+ }
+
+ if (nativeChild)
+ return nativeChild;
+
+ // if we didn't find anything, return ourself or child view.
+ return GetObjectOrRepresentedView(self);
+}
+
+- (NSArray*)accessibilityActionNames
+{
+ return nil;
+}
+
+- (NSString*)accessibilityActionDescription:(NSString*)action
+{
+ // by default we return whatever the MacOS API know about.
+ // if you have custom actions, override.
+ return NSAccessibilityActionDescription(action);
+}
+
+- (void)accessibilityPerformAction:(NSString*)action
+{
+}
+
+- (id)accessibilityFocusedUIElement
+{
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ if (!accWrap && !proxy)
+ return nil;
+
+ mozAccessible* focusedChild = nil;
+ if (accWrap) {
+ Accessible* focusedGeckoChild = accWrap->FocusedChild();
+ if (focusedGeckoChild)
+ focusedChild = GetNativeFromGeckoAccessible(focusedGeckoChild);
+ } else if (proxy) {
+ ProxyAccessible* focusedGeckoChild = proxy->FocusedChild();
+ if (focusedGeckoChild)
+ focusedChild = GetNativeFromProxy(focusedGeckoChild);
+ }
+
+ if (focusedChild)
+ return GetObjectOrRepresentedView(focusedChild);
+
+ // return ourself if we can't get a native focused child.
+ return GetObjectOrRepresentedView(self);
+}
+
+#pragma mark -
+
+- (id <mozAccessible>)parent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ id nativeParent = nil;
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ Accessible* accessibleParent = accWrap->Parent();
+ if (accessibleParent)
+ nativeParent = GetNativeFromGeckoAccessible(accessibleParent);
+ if (nativeParent)
+ return GetObjectOrRepresentedView(nativeParent);
+
+ // Return native of root accessible if we have no direct parent
+ nativeParent = GetNativeFromGeckoAccessible(accWrap->RootAccessible());
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ if (ProxyAccessible* proxyParent = proxy->Parent()) {
+ nativeParent = GetNativeFromProxy(proxyParent);
+ }
+
+ if (nativeParent)
+ return GetObjectOrRepresentedView(nativeParent);
+
+ Accessible* outerDoc = proxy->OuterDocOfRemoteBrowser();
+ nativeParent = outerDoc ?
+ GetNativeFromGeckoAccessible(outerDoc) : nil;
+ } else {
+ return nil;
+ }
+
+ NSAssert1 (nativeParent, @"!!! we can't find a parent for %@", self);
+
+ return GetObjectOrRepresentedView(nativeParent);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL)hasRepresentedView
+{
+ return NO;
+}
+
+- (id)representedView
+{
+ return nil;
+}
+
+- (BOOL)isRoot
+{
+ return NO;
+}
+
+// gets our native children lazily.
+// returns nil when there are no children.
+- (NSArray*)children
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (mChildren)
+ return mChildren;
+
+ // get the array of children.
+ mChildren = [[NSMutableArray alloc] init];
+
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ if (accWrap) {
+ uint32_t childCount = accWrap->ChildCount();
+ for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
+ mozAccessible* nativeChild = GetNativeFromGeckoAccessible(accWrap->GetChildAt(childIdx));
+ if (nativeChild)
+ [mChildren addObject:nativeChild];
+ }
+
+ // children from child if this is an outerdoc
+ OuterDocAccessible* docOwner = accWrap->AsOuterDoc();
+ if (docOwner) {
+ if (ProxyAccessible* proxyDoc = docOwner->RemoteChildDoc()) {
+ mozAccessible* nativeRemoteChild = GetNativeFromProxy(proxyDoc);
+ [mChildren insertObject:nativeRemoteChild atIndex:0];
+ NSAssert1 (nativeRemoteChild, @"%@ found a child remote doc missing a native\n", self);
+ }
+ }
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ uint32_t childCount = proxy->ChildrenCount();
+ for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
+ mozAccessible* nativeChild = GetNativeFromProxy(proxy->ChildAt(childIdx));
+ if (nativeChild)
+ [mChildren addObject:nativeChild];
+ }
+
+ }
+
+ return mChildren;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (NSValue*)position
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ nsIntRect rect;
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ rect = accWrap->Bounds();
+ else if (ProxyAccessible* proxy = [self getProxyAccessible])
+ rect = proxy->Bounds();
+ else
+ return nil;
+
+ NSScreen* mainView = [[NSScreen screens] objectAtIndex:0];
+ CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mainView);
+ NSPoint p = NSMakePoint(static_cast<CGFloat>(rect.x) / scaleFactor,
+ [mainView frame].size.height - static_cast<CGFloat>(rect.y + rect.height) / scaleFactor);
+
+ return [NSValue valueWithPoint:p];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (NSValue*)size
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ nsIntRect rect;
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ rect = accWrap->Bounds();
+ else if (ProxyAccessible* proxy = [self getProxyAccessible])
+ rect = proxy->Bounds();
+ else
+ return nil;
+
+ CGFloat scaleFactor =
+ nsCocoaUtils::GetBackingScaleFactor([[NSScreen screens] objectAtIndex:0]);
+ return [NSValue valueWithSize:NSMakeSize(static_cast<CGFloat>(rect.width) / scaleFactor,
+ static_cast<CGFloat>(rect.height) / scaleFactor)];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (NSString*)role
+{
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ if (accWrap) {
+ #ifdef DEBUG_A11Y
+ NS_ASSERTION(nsAccUtils::IsTextInterfaceSupportCorrect(accWrap),
+ "Does not support Text when it should");
+ #endif
+ } else if (![self getProxyAccessible]) {
+ return nil;
+ }
+
+#define ROLE(geckoRole, stringRole, atkRole, macRole, msaaRole, ia2Role, nameRule) \
+ case roles::geckoRole: \
+ return macRole;
+
+ switch (mRole) {
+#include "RoleMap.h"
+ default:
+ NS_NOTREACHED("Unknown role.");
+ return NSAccessibilityUnknownRole;
+ }
+
+#undef ROLE
+}
+
+- (NSString*)subrole
+{
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+
+ // Deal with landmarks first
+ nsIAtom* landmark = nullptr;
+ if (accWrap)
+ landmark = accWrap->LandmarkRole();
+ else if (proxy)
+ landmark = proxy->LandmarkRole();
+
+ if (landmark) {
+ if (landmark == nsGkAtoms::application)
+ return @"AXLandmarkApplication";
+ if (landmark == nsGkAtoms::banner)
+ return @"AXLandmarkBanner";
+ if (landmark == nsGkAtoms::complementary)
+ return @"AXLandmarkComplementary";
+ if (landmark == nsGkAtoms::contentinfo)
+ return @"AXLandmarkContentInfo";
+ if (landmark == nsGkAtoms::form)
+ return @"AXLandmarkForm";
+ if (landmark == nsGkAtoms::main)
+ return @"AXLandmarkMain";
+ if (landmark == nsGkAtoms::navigation)
+ return @"AXLandmarkNavigation";
+ if (landmark == nsGkAtoms::search)
+ return @"AXLandmarkSearch";
+ if (landmark == nsGkAtoms::searchbox)
+ return @"AXSearchField";
+ }
+
+ // Now, deal with widget roles
+ nsIAtom* roleAtom = nullptr;
+ if (accWrap && accWrap->HasARIARole()) {
+ const nsRoleMapEntry* roleMap = accWrap->ARIARoleMap();
+ roleAtom = *roleMap->roleAtom;
+ }
+ if (proxy)
+ roleAtom = proxy->ARIARoleAtom();
+
+ if (roleAtom) {
+ if (roleAtom == nsGkAtoms::alert)
+ return @"AXApplicationAlert";
+ if (roleAtom == nsGkAtoms::alertdialog)
+ return @"AXApplicationAlertDialog";
+ if (roleAtom == nsGkAtoms::article)
+ return @"AXDocumentArticle";
+ if (roleAtom == nsGkAtoms::dialog)
+ return @"AXApplicationDialog";
+ if (roleAtom == nsGkAtoms::document)
+ return @"AXDocument";
+ if (roleAtom == nsGkAtoms::log_)
+ return @"AXApplicationLog";
+ if (roleAtom == nsGkAtoms::marquee)
+ return @"AXApplicationMarquee";
+ if (roleAtom == nsGkAtoms::math)
+ return @"AXDocumentMath";
+ if (roleAtom == nsGkAtoms::note_)
+ return @"AXDocumentNote";
+ if (roleAtom == nsGkAtoms::region)
+ return @"AXDocumentRegion";
+ if (roleAtom == nsGkAtoms::status)
+ return @"AXApplicationStatus";
+ if (roleAtom == nsGkAtoms::tabpanel)
+ return @"AXTabPanel";
+ if (roleAtom == nsGkAtoms::timer)
+ return @"AXApplicationTimer";
+ if (roleAtom == nsGkAtoms::tooltip)
+ return @"AXUserInterfaceTooltip";
+ }
+
+ switch (mRole) {
+ case roles::LIST:
+ return @"AXContentList"; // 10.6+ NSAccessibilityContentListSubrole;
+
+ case roles::ENTRY:
+ if ((accWrap && accWrap->IsSearchbox()) ||
+ (proxy && proxy->IsSearchbox()))
+ return @"AXSearchField";
+ break;
+
+ case roles::DEFINITION_LIST:
+ return @"AXDefinitionList"; // 10.6+ NSAccessibilityDefinitionListSubrole;
+
+ case roles::TERM:
+ return @"AXTerm";
+
+ case roles::DEFINITION:
+ return @"AXDefinition";
+
+ case roles::MATHML_MATH:
+ return @"AXDocumentMath";
+
+ case roles::MATHML_FRACTION:
+ return @"AXMathFraction";
+
+ case roles::MATHML_FENCED:
+ // XXX bug 1176970
+ // This should be AXMathFence, but doing so without implementing the
+ // whole fence interface seems to make VoiceOver crash, so we present it
+ // as a row for now.
+ return @"AXMathRow";
+
+ case roles::MATHML_SUB:
+ case roles::MATHML_SUP:
+ case roles::MATHML_SUB_SUP:
+ return @"AXMathSubscriptSuperscript";
+
+ case roles::MATHML_ROW:
+ case roles::MATHML_STYLE:
+ case roles::MATHML_ERROR:
+ return @"AXMathRow";
+
+ case roles::MATHML_UNDER:
+ case roles::MATHML_OVER:
+ case roles::MATHML_UNDER_OVER:
+ return @"AXMathUnderOver";
+
+ case roles::MATHML_SQUARE_ROOT:
+ return @"AXMathSquareRoot";
+
+ case roles::MATHML_ROOT:
+ return @"AXMathRoot";
+
+ case roles::MATHML_TEXT:
+ return @"AXMathText";
+
+ case roles::MATHML_NUMBER:
+ return @"AXMathNumber";
+
+ case roles::MATHML_IDENTIFIER:
+ return @"AXMathIdentifier";
+
+ case roles::MATHML_TABLE:
+ return @"AXMathTable";
+
+ case roles::MATHML_TABLE_ROW:
+ return @"AXMathTableRow";
+
+ case roles::MATHML_CELL:
+ return @"AXMathTableCell";
+
+ // XXX: NSAccessibility also uses subroles AXMathSeparatorOperator and
+ // AXMathFenceOperator. We should use the NS_MATHML_OPERATOR_FENCE and
+ // NS_MATHML_OPERATOR_SEPARATOR bits of nsOperatorFlags, but currently they
+ // are only available from the MathML layout code. Hence we just fallback
+ // to subrole AXMathOperator for now.
+ // XXX bug 1175747 WebKit also creates anonymous operators for <mfenced>
+ // which have subroles AXMathSeparatorOperator and AXMathFenceOperator.
+ case roles::MATHML_OPERATOR:
+ return @"AXMathOperator";
+
+ case roles::MATHML_MULTISCRIPTS:
+ return @"AXMathMultiscript";
+
+ case roles::SWITCH:
+ return @"AXSwitch";
+
+ case roles::ALERT:
+ return @"AXApplicationAlert";
+
+ case roles::SEPARATOR:
+ return @"AXContentSeparator";
+
+ case roles::PROPERTYPAGE:
+ return @"AXTabPanel";
+
+ case roles::DETAILS:
+ return @"AXDetails";
+
+ case roles::SUMMARY:
+ return @"AXSummary";
+
+ default:
+ break;
+ }
+
+ return nil;
+}
+
+struct RoleDescrMap
+{
+ NSString* role;
+ const nsString description;
+};
+
+static const RoleDescrMap sRoleDescrMap[] = {
+ { @"AXApplicationAlert", NS_LITERAL_STRING("alert") },
+ { @"AXApplicationAlertDialog", NS_LITERAL_STRING("alertDialog") },
+ { @"AXApplicationLog", NS_LITERAL_STRING("log") },
+ { @"AXApplicationMarquee", NS_LITERAL_STRING("marquee") },
+ { @"AXApplicationStatus", NS_LITERAL_STRING("status") },
+ { @"AXApplicationTimer", NS_LITERAL_STRING("timer") },
+ { @"AXContentSeparator", NS_LITERAL_STRING("separator") },
+ { @"AXDefinition", NS_LITERAL_STRING("definition") },
+ { @"AXDocument", NS_LITERAL_STRING("document") },
+ { @"AXDocumentArticle", NS_LITERAL_STRING("article") },
+ { @"AXDocumentMath", NS_LITERAL_STRING("math") },
+ { @"AXDocumentNote", NS_LITERAL_STRING("note") },
+ { @"AXDocumentRegion", NS_LITERAL_STRING("region") },
+ { @"AXLandmarkApplication", NS_LITERAL_STRING("application") },
+ { @"AXLandmarkBanner", NS_LITERAL_STRING("banner") },
+ { @"AXLandmarkComplementary", NS_LITERAL_STRING("complementary") },
+ { @"AXLandmarkContentInfo", NS_LITERAL_STRING("content") },
+ { @"AXLandmarkMain", NS_LITERAL_STRING("main") },
+ { @"AXLandmarkNavigation", NS_LITERAL_STRING("navigation") },
+ { @"AXLandmarkSearch", NS_LITERAL_STRING("search") },
+ { @"AXSearchField", NS_LITERAL_STRING("searchTextField") },
+ { @"AXTabPanel", NS_LITERAL_STRING("tabPanel") },
+ { @"AXTerm", NS_LITERAL_STRING("term") },
+ { @"AXUserInterfaceTooltip", NS_LITERAL_STRING("tooltip") }
+};
+
+struct RoleDescrComparator
+{
+ const NSString* mRole;
+ explicit RoleDescrComparator(const NSString* aRole) : mRole(aRole) {}
+ int operator()(const RoleDescrMap& aEntry) const {
+ return [mRole compare:aEntry.role];
+ }
+};
+
+- (NSString*)roleDescription
+{
+ if (mRole == roles::DOCUMENT)
+ return utils::LocalizedString(NS_LITERAL_STRING("htmlContent"));
+
+ NSString* subrole = [self subrole];
+
+ if (subrole) {
+ size_t idx = 0;
+ if (BinarySearchIf(sRoleDescrMap, 0, ArrayLength(sRoleDescrMap),
+ RoleDescrComparator(subrole), &idx)) {
+ return utils::LocalizedString(sRoleDescrMap[idx].description);
+ }
+ }
+
+ return NSAccessibilityRoleDescription([self role], subrole);
+}
+
+- (NSString*)title
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ nsAutoString title;
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ accWrap->Name(title);
+ else if (ProxyAccessible* proxy = [self getProxyAccessible])
+ proxy->Name(title);
+
+ return nsCocoaUtils::ToNSString(title);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)value
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ nsAutoString value;
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ accWrap->Value(value);
+ else if (ProxyAccessible* proxy = [self getProxyAccessible])
+ proxy->Value(value);
+
+ return nsCocoaUtils::ToNSString(value);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)valueDidChange
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+#ifdef DEBUG_hakan
+ NSLog(@"%@'s value changed!", self);
+#endif
+ // sending out a notification is expensive, so we don't do it other than for really important objects,
+ // like mozTextAccessible.
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)selectedTextDidChange
+{
+ // Do nothing. mozTextAccessible will.
+}
+
+- (void)documentLoadComplete
+{
+ id realSelf = GetObjectOrRepresentedView(self);
+ NSAccessibilityPostNotification(realSelf, NSAccessibilityFocusedUIElementChangedNotification);
+ NSAccessibilityPostNotification(realSelf, @"AXLoadComplete");
+ NSAccessibilityPostNotification(realSelf, @"AXLayoutComplete");
+}
+
+- (NSString*)help
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ // What needs to go here is actually the accDescription of an item.
+ // The MSAA acc_help method has nothing to do with this one.
+ nsAutoString helpText;
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ accWrap->Description(helpText);
+ else if (ProxyAccessible* proxy = [self getProxyAccessible])
+ proxy->Description(helpText);
+
+ return nsCocoaUtils::ToNSString(helpText);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// objc-style description (from NSObject); not to be confused with the accessible description above.
+- (NSString*)description
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [NSString stringWithFormat:@"(%p) %@", self, [self role]];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL)isFocused
+{
+ return FocusMgr()->IsFocused([self getGeckoAccessible]);
+}
+
+- (BOOL)canBeFocused
+{
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ return accWrap->InteractiveState() & states::FOCUSABLE;
+
+ if (ProxyAccessible* proxy = [self getProxyAccessible])
+ return proxy->State() & states::FOCUSABLE;
+
+ return false;
+}
+
+- (BOOL)focus
+{
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ accWrap->TakeFocus();
+ else if (ProxyAccessible* proxy = [self getProxyAccessible])
+ proxy->TakeFocus();
+ else
+ return NO;
+
+ return YES;
+}
+
+- (BOOL)isEnabled
+{
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ return ((accWrap->InteractiveState() & states::UNAVAILABLE) == 0);
+
+ if (ProxyAccessible* proxy = [self getProxyAccessible])
+ return ((proxy->State() & states::UNAVAILABLE) == 0);
+
+ return false;
+}
+
+// The root accessible calls this when the focused node was
+// changed to us.
+- (void)didReceiveFocus
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+#ifdef DEBUG_hakan
+ NSLog (@"%@ received focus!", self);
+#endif
+ NSAccessibilityPostNotification(GetObjectOrRepresentedView(self),
+ NSAccessibilityFocusedUIElementChangedNotification);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (NSWindow*)window
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ // Get a pointer to the native window (NSWindow) we reside in.
+ NSWindow *nativeWindow = nil;
+ DocAccessible* docAcc = nullptr;
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ docAcc = accWrap->Document();
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ Accessible* outerDoc = proxy->OuterDocOfRemoteBrowser();
+ if (outerDoc)
+ docAcc = outerDoc->Document();
+ }
+
+ if (docAcc)
+ nativeWindow = static_cast<NSWindow*>(docAcc->GetNativeWindow());
+
+ NSAssert1(nativeWindow, @"Could not get native window for %@", self);
+ return nativeWindow;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)invalidateChildren
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // make room for new children
+ [mChildren release];
+ mChildren = nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)appendChild:(Accessible*)aAccessible
+{
+ // if mChildren is nil, then we don't even need to bother
+ if (!mChildren)
+ return;
+
+ mozAccessible *curNative = GetNativeFromGeckoAccessible(aAccessible);
+ if (curNative)
+ [mChildren addObject:curNative];
+}
+
+- (void)expire
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [self invalidateChildren];
+
+ mGeckoAccessible = 0;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (BOOL)isExpired
+{
+ return ![self getGeckoAccessible] && ![self getProxyAccessible];
+}
+
+#pragma mark -
+#pragma mark Debug methods
+#pragma mark -
+
+#ifdef DEBUG
+
+// will check that our children actually reference us as their
+// parent.
+- (void)sanityCheckChildren:(NSArray *)children
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSEnumerator *iter = [children objectEnumerator];
+ mozAccessible *curObj = nil;
+
+ NSLog(@"sanity checking %@", self);
+
+ while ((curObj = [iter nextObject])) {
+ id realSelf = GetObjectOrRepresentedView(self);
+ NSLog(@"checking %@", realSelf);
+ NSAssert2([curObj parent] == realSelf,
+ @"!!! %@ not returning %@ as AXParent, even though it is a AXChild of it!", curObj, realSelf);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)sanityCheckChildren
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [self sanityCheckChildren:[self children]];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)printHierarchy
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [self printHierarchyWithLevel:0];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)printHierarchyWithLevel:(unsigned)level
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSAssert(![self isExpired], @"!!! trying to print hierarchy of expired object!");
+
+ // print this node
+ NSMutableString *indent = [NSMutableString stringWithCapacity:level];
+ unsigned i=0;
+ for (;i<level;i++)
+ [indent appendString:@" "];
+
+ NSLog (@"%@(#%i) %@", indent, level, self);
+
+ // use |children| method to make sure our children are lazily fetched first.
+ NSArray *children = [self children];
+ if (!children)
+ return;
+
+ [self sanityCheckChildren];
+
+ NSEnumerator *iter = [children objectEnumerator];
+ mozAccessible *object = nil;
+
+ while (iter && (object = [iter nextObject]))
+ // print every child node's subtree, increasing the indenting
+ // by two for every level.
+ [object printHierarchyWithLevel:(level+1)];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+#endif /* DEBUG */
+
+@end
diff --git a/accessible/mac/mozAccessibleProtocol.h b/accessible/mac/mozAccessibleProtocol.h
new file mode 100644
index 000000000..5f67b1dcf
--- /dev/null
+++ b/accessible/mac/mozAccessibleProtocol.h
@@ -0,0 +1,69 @@
+/* -*- Mode: Objective-C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#import <Cocoa/Cocoa.h>
+
+#import "mozView.h"
+
+/* This protocol's primary use is so widget/cocoa can talk back to us
+ properly.
+
+ ChildView owns the topmost mozRootAccessible, and needs to take care of setting up
+ that parent/child relationship.
+
+ This protocol is thus used to make sure it knows it's talking to us, and not
+ just some random |id|.
+*/
+
+@protocol mozAccessible
+
+// returns whether this accessible is the root accessible. there is one
+// root accessible per window.
+- (BOOL)isRoot;
+
+// some mozAccessibles implement accessibility support in place of another object. for example,
+// ChildView gets its support from us.
+//
+// instead of returning a mozAccessible to the OS when it wants an object, we need to pass the view we represent, so the
+// OS doesn't get confused and think we return some random object.
+- (BOOL)hasRepresentedView;
+- (id)representedView;
+
+#ifdef DEBUG
+// debug utility that will print the native accessibility tree, starting
+// at this node.
+- (void)printHierarchy;
+#endif
+
+/*** general ***/
+
+// returns the accessible at the specified point.
+- (id)accessibilityHitTest:(NSPoint)point;
+
+// whether this element is flagged as ignored.
+- (BOOL)accessibilityIsIgnored;
+
+// currently focused UI element (possibly a child accessible)
+- (id)accessibilityFocusedUIElement;
+
+/*** attributes ***/
+
+// all supported attributes
+- (NSArray*)accessibilityAttributeNames;
+
+// value for given attribute.
+- (id)accessibilityAttributeValue:(NSString*)attribute;
+
+// whether a particular attribute can be modified
+- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute;
+
+/*** actions ***/
+
+- (NSArray*)accessibilityActionNames;
+- (NSString*)accessibilityActionDescription:(NSString*)action;
+- (void)accessibilityPerformAction:(NSString*)action;
+
+@end
+
diff --git a/accessible/mac/mozActionElements.h b/accessible/mac/mozActionElements.h
new file mode 100644
index 000000000..a325921eb
--- /dev/null
+++ b/accessible/mac/mozActionElements.h
@@ -0,0 +1,37 @@
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#import <Cocoa/Cocoa.h>
+#import "mozAccessible.h"
+
+/* Simple subclasses for things like checkboxes, buttons, etc. */
+
+@interface mozButtonAccessible : mozAccessible
+ {
+ }
+- (BOOL)hasPopup;
+- (void)click;
+- (BOOL)isTab;
+@end
+
+@interface mozCheckboxAccessible : mozButtonAccessible
+// returns one of the constants defined in CheckboxValue
+- (int)isChecked;
+@end
+
+/* Class for tabs - not individual tabs */
+@interface mozTabsAccessible : mozAccessible
+{
+ NSMutableArray* mTabs;
+}
+-(id)tabs;
+@end
+
+/**
+ * Accessible for a PANE
+ */
+@interface mozPaneAccessible : mozAccessible
+
+@end
diff --git a/accessible/mac/mozActionElements.mm b/accessible/mac/mozActionElements.mm
new file mode 100644
index 000000000..5decd6ccc
--- /dev/null
+++ b/accessible/mac/mozActionElements.mm
@@ -0,0 +1,340 @@
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#import "mozActionElements.h"
+
+#import "MacUtils.h"
+#include "Accessible-inl.h"
+#include "DocAccessible.h"
+#include "XULTabAccessible.h"
+
+#include "nsDeckFrame.h"
+#include "nsObjCExceptions.h"
+
+using namespace mozilla::a11y;
+
+enum CheckboxValue {
+ // these constants correspond to the values in the OS
+ kUnchecked = 0,
+ kChecked = 1,
+ kMixed = 2
+};
+
+@implementation mozButtonAccessible
+
+- (NSArray*)accessibilityAttributeNames
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ static NSArray *attributes = nil;
+ if (!attributes) {
+ attributes = [[NSArray alloc] initWithObjects:NSAccessibilityParentAttribute, // required
+ NSAccessibilityRoleAttribute, // required
+ NSAccessibilityRoleDescriptionAttribute,
+ NSAccessibilityPositionAttribute, // required
+ NSAccessibilitySizeAttribute, // required
+ NSAccessibilityWindowAttribute, // required
+ NSAccessibilityPositionAttribute, // required
+ NSAccessibilityTopLevelUIElementAttribute, // required
+ NSAccessibilityHelpAttribute,
+ NSAccessibilityEnabledAttribute, // required
+ NSAccessibilityFocusedAttribute, // required
+ NSAccessibilityTitleAttribute, // required
+ NSAccessibilityChildrenAttribute,
+ NSAccessibilityDescriptionAttribute,
+#if DEBUG
+ @"AXMozDescription",
+#endif
+ nil];
+ }
+ return attributes;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)accessibilityAttributeValue:(NSString *)attribute
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) {
+ if ([self hasPopup])
+ return [self children];
+ return nil;
+ }
+
+ if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute]) {
+ if ([self isTab])
+ return utils::LocalizedString(NS_LITERAL_STRING("tab"));
+
+ return NSAccessibilityRoleDescription([self role], nil);
+ }
+
+ return [super accessibilityAttributeValue:attribute];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL)accessibilityIsIgnored
+{
+ return ![self getGeckoAccessible] && ![self getProxyAccessible];
+}
+
+- (NSArray*)accessibilityActionNames
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ([self isEnabled]) {
+ if ([self hasPopup])
+ return [NSArray arrayWithObjects:NSAccessibilityPressAction,
+ NSAccessibilityShowMenuAction,
+ nil];
+ return [NSArray arrayWithObject:NSAccessibilityPressAction];
+ }
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (NSString*)accessibilityActionDescription:(NSString*)action
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ([action isEqualToString:NSAccessibilityPressAction]) {
+ if ([self isTab])
+ return utils::LocalizedString(NS_LITERAL_STRING("switch"));
+
+ return @"press button"; // XXX: localize this later?
+ }
+
+ if ([self hasPopup]) {
+ if ([action isEqualToString:NSAccessibilityShowMenuAction])
+ return @"show menu";
+ }
+
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)accessibilityPerformAction:(NSString*)action
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if ([self isEnabled] && [action isEqualToString:NSAccessibilityPressAction]) {
+ // TODO: this should bring up the menu, but currently doesn't.
+ // once msaa and atk have merged better, they will implement
+ // the action needed to show the menu.
+ [self click];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)click
+{
+ // both buttons and checkboxes have only one action. we should really stop using arbitrary
+ // arrays with actions, and define constants for these actions.
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ accWrap->DoAction(0);
+ else if (ProxyAccessible* proxy = [self getProxyAccessible])
+ proxy->DoAction(0);
+}
+
+- (BOOL)isTab
+{
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ return accWrap->Role() == roles::PAGETAB;
+
+ if (ProxyAccessible* proxy = [self getProxyAccessible])
+ return proxy->Role() == roles::PAGETAB;
+
+ return false;
+}
+
+- (BOOL)hasPopup
+{
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ return accWrap->NativeState() & states::HASPOPUP;
+
+ if (ProxyAccessible* proxy = [self getProxyAccessible])
+ return proxy->NativeState() & states::HASPOPUP;
+
+ return false;
+}
+
+@end
+
+@implementation mozCheckboxAccessible
+
+- (NSString*)accessibilityActionDescription:(NSString*)action
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ([action isEqualToString:NSAccessibilityPressAction]) {
+ if ([self isChecked] != kUnchecked)
+ return @"uncheck checkbox"; // XXX: localize this later?
+
+ return @"check checkbox"; // XXX: localize this later?
+ }
+
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (int)isChecked
+{
+ uint64_t state = 0;
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ state = accWrap->NativeState();
+ else if (ProxyAccessible* proxy = [self getProxyAccessible])
+ state = proxy->NativeState();
+
+ // check if we're checked or in a mixed state
+ if (state & states::CHECKED) {
+ return (state & states::MIXED) ? kMixed : kChecked;
+ }
+
+ return kUnchecked;
+}
+
+- (id)value
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [NSNumber numberWithInt:[self isChecked]];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+@end
+
+@implementation mozTabsAccessible
+
+- (void)dealloc
+{
+ [mTabs release];
+
+ [super dealloc];
+}
+
+- (NSArray*)accessibilityAttributeNames
+{
+ // standard attributes that are shared and supported by root accessible (AXMain) elements.
+ static NSMutableArray* attributes = nil;
+
+ if (!attributes) {
+ attributes = [[super accessibilityAttributeNames] mutableCopy];
+ [attributes addObject:NSAccessibilityContentsAttribute];
+ [attributes addObject:NSAccessibilityTabsAttribute];
+ }
+
+ return attributes;
+}
+
+- (id)accessibilityAttributeValue:(NSString *)attribute
+{
+ if ([attribute isEqualToString:NSAccessibilityContentsAttribute])
+ return [super children];
+ if ([attribute isEqualToString:NSAccessibilityTabsAttribute])
+ return [self tabs];
+
+ return [super accessibilityAttributeValue:attribute];
+}
+
+/**
+ * Returns the selected tab (the mozAccessible)
+ */
+- (id)value
+{
+ mozAccessible* nativeAcc = nil;
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ if (Accessible* accTab = accWrap->GetSelectedItem(0)) {
+ accTab->GetNativeInterface((void**)&nativeAcc);
+ }
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ if (ProxyAccessible* proxyTab = proxy->GetSelectedItem(0)) {
+ nativeAcc = GetNativeFromProxy(proxyTab);
+ }
+ }
+
+ return nativeAcc;
+}
+
+/**
+ * Return the mozAccessibles that are the tabs.
+ */
+- (id)tabs
+{
+ if (mTabs)
+ return mTabs;
+
+ NSArray* children = [self children];
+ NSEnumerator* enumerator = [children objectEnumerator];
+ mTabs = [[NSMutableArray alloc] init];
+
+ id obj;
+ while ((obj = [enumerator nextObject]))
+ if ([obj isTab])
+ [mTabs addObject:obj];
+
+ return mTabs;
+}
+
+- (void)invalidateChildren
+{
+ [super invalidateChildren];
+
+ [mTabs release];
+ mTabs = nil;
+}
+
+@end
+
+@implementation mozPaneAccessible
+
+- (NSUInteger)accessibilityArrayAttributeCount:(NSString*)attribute
+{
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ if (!accWrap && !proxy)
+ return 0;
+
+ // By default this calls -[[mozAccessible children] count].
+ // Since we don't cache mChildren. This is faster.
+ if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) {
+ if (accWrap)
+ return accWrap->ChildCount() ? 1 : 0;
+
+ return proxy->ChildrenCount() ? 1 : 0;
+ }
+
+ return [super accessibilityArrayAttributeCount:attribute];
+}
+
+- (NSArray*)children
+{
+ if (![self getGeckoAccessible])
+ return nil;
+
+ nsDeckFrame* deckFrame = do_QueryFrame([self getGeckoAccessible]->GetFrame());
+ nsIFrame* selectedFrame = deckFrame ? deckFrame->GetSelectedBox() : nullptr;
+
+ Accessible* selectedAcc = nullptr;
+ if (selectedFrame) {
+ nsINode* node = selectedFrame->GetContent();
+ selectedAcc = [self getGeckoAccessible]->Document()->GetAccessible(node);
+ }
+
+ if (selectedAcc) {
+ mozAccessible *curNative = GetNativeFromGeckoAccessible(selectedAcc);
+ if (curNative)
+ return [NSArray arrayWithObjects:GetObjectOrRepresentedView(curNative), nil];
+ }
+
+ return nil;
+}
+
+@end
diff --git a/accessible/mac/mozDocAccessible.h b/accessible/mac/mozDocAccessible.h
new file mode 100644
index 000000000..c38177311
--- /dev/null
+++ b/accessible/mac/mozDocAccessible.h
@@ -0,0 +1,31 @@
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#import <Cocoa/Cocoa.h>
+#import "mozAccessible.h"
+
+// our protocol that we implement (so cocoa widgets can talk to us)
+#import "mozAccessibleProtocol.h"
+
+/*
+ The root accessible. There is one per window.
+ Created by the RootAccessibleWrap.
+*/
+@interface mozRootAccessible : mozAccessible
+{
+ // the mozView that we're representing.
+ // all outside communication goes through the mozView.
+ // in reality, it's just piping all calls to us, and we're
+ // doing its dirty work!
+ //
+ // whenever someone asks who we are (e.g., a child asking
+ // for its parent, or our parent asking for its child), we'll
+ // respond the mozView. it is absolutely necessary for third-
+ // party tools that we do this!
+ //
+ // /hwaara
+ id <mozView, mozAccessible> mParallelView; // weak ref
+}
+@end
diff --git a/accessible/mac/mozDocAccessible.mm b/accessible/mac/mozDocAccessible.mm
new file mode 100644
index 000000000..4bae81f01
--- /dev/null
+++ b/accessible/mac/mozDocAccessible.mm
@@ -0,0 +1,111 @@
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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"
+
+#import "mozDocAccessible.h"
+
+#import "mozView.h"
+
+// This must be included last:
+#include "nsObjCExceptions.h"
+
+using namespace mozilla::a11y;
+
+static id <mozAccessible, mozView>
+getNativeViewFromRootAccessible(Accessible* aAccessible)
+{
+ RootAccessibleWrap* root =
+ static_cast<RootAccessibleWrap*>(aAccessible->AsRoot());
+ id <mozAccessible, mozView> nativeView = nil;
+ root->GetNativeWidget ((void**)&nativeView);
+ return nativeView;
+}
+
+#pragma mark -
+
+@implementation mozRootAccessible
+
+- (NSArray*)accessibilityAttributeNames
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ // if we're expired, we don't support any attributes.
+ if (![self getGeckoAccessible])
+ return [NSArray array];
+
+ // standard attributes that are shared and supported by root accessible (AXMain) elements.
+ static NSMutableArray* attributes = nil;
+
+ if (!attributes) {
+ attributes = [[super accessibilityAttributeNames] mutableCopy];
+ [attributes addObject:NSAccessibilityMainAttribute];
+ [attributes addObject:NSAccessibilityMinimizedAttribute];
+ }
+
+ return attributes;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)accessibilityAttributeValue:(NSString *)attribute
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ([attribute isEqualToString:NSAccessibilityMainAttribute])
+ return [NSNumber numberWithBool:[[self window] isMainWindow]];
+ if ([attribute isEqualToString:NSAccessibilityMinimizedAttribute])
+ return [NSNumber numberWithBool:[[self window] isMiniaturized]];
+
+ return [super accessibilityAttributeValue:attribute];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+
+// return the AXParent that our parallell NSView tells us about.
+- (id)parent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!mParallelView)
+ mParallelView = (id<mozView, mozAccessible>)[self representedView];
+
+ if (mParallelView)
+ return [mParallelView accessibilityAttributeValue:NSAccessibilityParentAttribute];
+
+ NSAssert(mParallelView, @"we're a root accessible w/o native view?");
+ return [super parent];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL)hasRepresentedView
+{
+ return YES;
+}
+
+// this will return our parallell NSView. see mozDocAccessible.h
+- (id)representedView
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (mParallelView)
+ return (id)mParallelView;
+
+ mParallelView = getNativeViewFromRootAccessible ([self getGeckoAccessible]);
+
+ NSAssert(mParallelView, @"can't return root accessible's native parallel view.");
+ return mParallelView;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL)isRoot
+{
+ return YES;
+}
+
+@end
diff --git a/accessible/mac/mozHTMLAccessible.h b/accessible/mac/mozHTMLAccessible.h
new file mode 100644
index 000000000..c70a3c2a2
--- /dev/null
+++ b/accessible/mac/mozHTMLAccessible.h
@@ -0,0 +1,16 @@
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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/. */
+
+#import "mozAccessible.h"
+
+@interface mozHeadingAccessible : mozAccessible
+
+@end
+
+@interface mozLinkAccessible : mozAccessible
+
+@end
diff --git a/accessible/mac/mozHTMLAccessible.mm b/accessible/mac/mozHTMLAccessible.mm
new file mode 100644
index 000000000..6c4925589
--- /dev/null
+++ b/accessible/mac/mozHTMLAccessible.mm
@@ -0,0 +1,139 @@
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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/. */
+
+#import "mozHTMLAccessible.h"
+
+#import "Accessible-inl.h"
+#import "HyperTextAccessible.h"
+
+#import "nsCocoaUtils.h"
+
+@implementation mozHeadingAccessible
+
+- (NSString*)title
+{
+ nsAutoString title;
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ mozilla::ErrorResult rv;
+ // XXX use the flattening API when there are available
+ // see bug 768298
+ accWrap->GetContent()->GetTextContent(title, rv);
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ proxy->Title(title);
+ }
+
+ return nsCocoaUtils::ToNSString(title);
+}
+
+- (id)value
+{
+ uint32_t level = 0;
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ level = accWrap->GetLevelInternal();
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ level = proxy->GetLevelInternal();
+ }
+
+ return [NSNumber numberWithInt:level];
+}
+
+@end
+
+@interface mozLinkAccessible ()
+-(NSURL*)url;
+@end
+
+@implementation mozLinkAccessible
+
+- (NSArray*)accessibilityAttributeNames
+{
+ // if we're expired, we don't support any attributes.
+ if (![self getGeckoAccessible] && ![self getProxyAccessible])
+ return [NSArray array];
+
+ static NSMutableArray* attributes = nil;
+
+ if (!attributes) {
+ attributes = [[super accessibilityAttributeNames] mutableCopy];
+ [attributes addObject:NSAccessibilityURLAttribute];
+ }
+
+ return attributes;
+}
+
+- (id)accessibilityAttributeValue:(NSString *)attribute
+{
+ if ([attribute isEqualToString:NSAccessibilityURLAttribute])
+ return [self url];
+
+ return [super accessibilityAttributeValue:attribute];
+}
+
+- (NSArray*)accessibilityActionNames
+{
+ // if we're expired, we don't support any attributes.
+ if (![self getGeckoAccessible] && ![self getProxyAccessible])
+ return [NSArray array];
+
+ static NSArray* actionNames = nil;
+
+ if (!actionNames) {
+ actionNames = [[NSArray alloc] initWithObjects:NSAccessibilityPressAction,
+ nil];
+ }
+
+ return actionNames;
+}
+
+- (void)accessibilityPerformAction:(NSString*)action
+{
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ if (!accWrap && !proxy) {
+ return;
+ }
+
+ if ([action isEqualToString:NSAccessibilityPressAction]) {
+ if (accWrap) {
+ accWrap->DoAction(0);
+ } else if (proxy) {
+ proxy->DoAction(0);
+ }
+ return;
+ }
+
+ [super accessibilityPerformAction:action];
+
+}
+
+- (NSString*)customDescription
+{
+ return @"";
+}
+
+- (NSString*)value
+{
+ return @"";
+}
+
+- (NSURL*)url
+{
+ nsAutoString value;
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ accWrap->Value(value);
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ proxy->Value(value);
+ }
+
+ NSString* urlString = value.IsEmpty() ? nil : nsCocoaUtils::ToNSString(value);
+ if (!urlString)
+ return nil;
+
+ return [NSURL URLWithString:urlString];
+}
+
+@end
diff --git a/accessible/mac/mozTableAccessible.h b/accessible/mac/mozTableAccessible.h
new file mode 100644
index 000000000..435b5adc5
--- /dev/null
+++ b/accessible/mac/mozTableAccessible.h
@@ -0,0 +1,28 @@
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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/. */
+
+#import "mozAccessible.h"
+
+@interface mozTablePartAccessible : mozAccessible
+- (BOOL)isLayoutTablePart;
+- (NSString*)role;
+@end
+
+@interface mozTableAccessible : mozTablePartAccessible
+- (NSArray*)additionalAccessibilityAttributeNames;
+- (id)accessibilityAttributeValue:(NSString*)attribute;
+@end
+
+@interface mozTableRowAccessible : mozTablePartAccessible
+- (NSArray*)additionalAccessibilityAttributeNames;
+- (id)accessibilityAttributeValue:(NSString*)attribute;
+@end
+
+@interface mozTableCellAccessible : mozTablePartAccessible
+- (NSArray*)additionalAccessibilityAttributeNames;
+- (id)accessibilityAttributeValue:(NSString*)attribute;
+@end
diff --git a/accessible/mac/mozTableAccessible.mm b/accessible/mac/mozTableAccessible.mm
new file mode 100644
index 000000000..a3612e5bc
--- /dev/null
+++ b/accessible/mac/mozTableAccessible.mm
@@ -0,0 +1,240 @@
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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/. */
+
+#import "mozTableAccessible.h"
+#import "nsCocoaUtils.h"
+
+@implementation mozTablePartAccessible
+- (BOOL)isLayoutTablePart;
+{
+ if (Accessible* accWrap = [self getGeckoAccessible]) {
+ while (accWrap) {
+ if (accWrap->IsTable()) {
+ return accWrap->AsTable()->IsProbablyLayoutTable();
+ }
+ accWrap = accWrap->Parent();
+ }
+ return false;
+ }
+
+ if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ while (proxy) {
+ if (proxy->IsTable()) {
+ return proxy->TableIsProbablyForLayout();
+ }
+ proxy = proxy->Parent();
+ }
+ }
+
+ return false;
+}
+
+- (NSString*)role
+{
+ return [self isLayoutTablePart] ? NSAccessibilityGroupRole : [super role];
+}
+@end
+
+@implementation mozTableAccessible
+- (NSArray*)additionalAccessibilityAttributeNames
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSArray* additionalAttributes = [super additionalAccessibilityAttributeNames];
+ if ([self isLayoutTablePart]) {
+ return additionalAttributes;
+ }
+
+ static NSArray* tableAttrs = nil;
+ if (!tableAttrs) {
+ NSMutableArray* tempArray = [NSMutableArray new];
+ [tempArray addObject:NSAccessibilityRowCountAttribute];
+ [tempArray addObject:NSAccessibilityColumnCountAttribute];
+ [tempArray addObject:NSAccessibilityRowsAttribute];
+ tableAttrs = [[NSArray alloc] initWithArray:tempArray];
+ [tempArray release];
+ }
+
+ return [additionalAttributes arrayByAddingObjectsFromArray:tableAttrs];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)accessibilityAttributeValue:(NSString*)attribute
+{
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ TableAccessible* table = accWrap->AsTable();
+ if ([attribute isEqualToString:NSAccessibilityRowCountAttribute])
+ return @(table->RowCount());
+ if ([attribute isEqualToString:NSAccessibilityColumnCountAttribute])
+ return @(table->ColCount());
+ if ([attribute isEqualToString:NSAccessibilityRowsAttribute]) {
+ // Create a new array with the list of table rows.
+ NSMutableArray* nativeArray = [[NSMutableArray alloc] init];
+ uint32_t totalCount = accWrap->ChildCount();
+ for (uint32_t i = 0; i < totalCount; i++) {
+ if (accWrap->GetChildAt(i)->IsTableRow()) {
+ mozAccessible* curNative =
+ GetNativeFromGeckoAccessible(accWrap->GetChildAt(i));
+ if (curNative)
+ [nativeArray addObject:GetObjectOrRepresentedView(curNative)];
+ }
+ }
+ return nativeArray;
+ }
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ if ([attribute isEqualToString:NSAccessibilityRowCountAttribute])
+ return @(proxy->TableRowCount());
+ if ([attribute isEqualToString:NSAccessibilityColumnCountAttribute])
+ return @(proxy->TableColumnCount());
+ if ([attribute isEqualToString:NSAccessibilityRowsAttribute]) {
+ // Create a new array with the list of table rows.
+ NSMutableArray* nativeArray = [[NSMutableArray alloc] init];
+ uint32_t totalCount = proxy->ChildrenCount();
+ for (uint32_t i = 0; i < totalCount; i++) {
+ if (proxy->ChildAt(i)->IsTableRow()) {
+ mozAccessible* curNative =
+ GetNativeFromProxy(proxy->ChildAt(i));
+ if (curNative)
+ [nativeArray addObject:GetObjectOrRepresentedView(curNative)];
+ }
+ }
+ return nativeArray;
+ }
+ }
+
+ return [super accessibilityAttributeValue:attribute];
+}
+@end
+
+@implementation mozTableRowAccessible
+- (NSArray*)additionalAccessibilityAttributeNames
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSArray* additionalAttributes = [super additionalAccessibilityAttributeNames];
+ if ([self isLayoutTablePart]) {
+ return additionalAttributes;
+ }
+
+ static NSArray* tableRowAttrs = nil;
+ if (!tableRowAttrs) {
+ NSMutableArray* tempArray = [NSMutableArray new];
+ [tempArray addObject:NSAccessibilityIndexAttribute];
+ tableRowAttrs = [[NSArray alloc] initWithArray:tempArray];
+ [tempArray release];
+ }
+
+ return [additionalAttributes arrayByAddingObjectsFromArray:tableRowAttrs];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)accessibilityAttributeValue:(NSString*)attribute
+{
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ if ([attribute isEqualToString:NSAccessibilityIndexAttribute]) {
+ // Count the number of rows before that one to obtain the row index.
+ uint32_t index = 0;
+ Accessible* parent = accWrap->Parent();
+ if (parent) {
+ for (int32_t i = accWrap->IndexInParent() - 1; i >= 0; i--) {
+ if (parent->GetChildAt(i)->IsTableRow()) {
+ index++;
+ }
+ }
+ }
+ return [NSNumber numberWithUnsignedInteger:index];
+ }
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ if ([attribute isEqualToString:NSAccessibilityIndexAttribute]) {
+ // Count the number of rows before that one to obtain the row index.
+ uint32_t index = 0;
+ ProxyAccessible* parent = proxy->Parent();
+ if (parent) {
+ for (int32_t i = proxy->IndexInParent() - 1; i >= 0; i--) {
+ if (parent->ChildAt(i)->IsTableRow()) {
+ index++;
+ }
+ }
+ }
+ return [NSNumber numberWithUnsignedInteger:index];
+ }
+ }
+
+ return [super accessibilityAttributeValue:attribute];
+}
+@end
+
+@implementation mozTableCellAccessible
+- (NSArray*)additionalAccessibilityAttributeNames
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSArray* additionalAttributes = [super additionalAccessibilityAttributeNames];
+ if ([self isLayoutTablePart]) {
+ return additionalAttributes;
+ }
+
+ static NSArray* tableCellAttrs = nil;
+ if (!tableCellAttrs) {
+ NSMutableArray* tempArray = [NSMutableArray new];
+ [tempArray addObject:NSAccessibilityRowIndexRangeAttribute];
+ [tempArray addObject:NSAccessibilityColumnIndexRangeAttribute];
+ [tempArray addObject:NSAccessibilityRowHeaderUIElementsAttribute];
+ [tempArray addObject:NSAccessibilityColumnHeaderUIElementsAttribute];
+ tableCellAttrs = [[NSArray alloc] initWithArray:tempArray];
+ [tempArray release];
+ }
+
+ return [additionalAttributes arrayByAddingObjectsFromArray:tableCellAttrs];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)accessibilityAttributeValue:(NSString*)attribute
+{
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ TableCellAccessible* cell = accWrap->AsTableCell();
+ if ([attribute isEqualToString:NSAccessibilityRowIndexRangeAttribute])
+ return [NSValue valueWithRange:NSMakeRange(cell->RowIdx(),
+ cell->RowExtent())];
+ if ([attribute isEqualToString:NSAccessibilityColumnIndexRangeAttribute])
+ return [NSValue valueWithRange:NSMakeRange(cell->ColIdx(),
+ cell->ColExtent())];
+ if ([attribute isEqualToString:NSAccessibilityRowHeaderUIElementsAttribute]) {
+ AutoTArray<Accessible*, 10> headerCells;
+ cell->RowHeaderCells(&headerCells);
+ return ConvertToNSArray(headerCells);
+ }
+ if ([attribute isEqualToString:NSAccessibilityColumnHeaderUIElementsAttribute]) {
+ AutoTArray<Accessible*, 10> headerCells;
+ cell->ColHeaderCells(&headerCells);
+ return ConvertToNSArray(headerCells);
+ }
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ if ([attribute isEqualToString:NSAccessibilityRowIndexRangeAttribute])
+ return [NSValue valueWithRange:NSMakeRange(proxy->RowIdx(),
+ proxy->RowExtent())];
+ if ([attribute isEqualToString:NSAccessibilityColumnIndexRangeAttribute])
+ return [NSValue valueWithRange:NSMakeRange(proxy->ColIdx(),
+ proxy->ColExtent())];
+ if ([attribute isEqualToString:NSAccessibilityRowHeaderUIElementsAttribute]) {
+ nsTArray<ProxyAccessible*> headerCells;
+ proxy->RowHeaderCells(&headerCells);
+ return ConvertToNSArray(headerCells);
+ }
+ if ([attribute isEqualToString:NSAccessibilityColumnHeaderUIElementsAttribute]) {
+ nsTArray<ProxyAccessible*> headerCells;
+ proxy->ColHeaderCells(&headerCells);
+ return ConvertToNSArray(headerCells);
+ }
+ }
+
+ return [super accessibilityAttributeValue:attribute];
+}
+@end
diff --git a/accessible/mac/mozTextAccessible.h b/accessible/mac/mozTextAccessible.h
new file mode 100644
index 000000000..8bc23ae8d
--- /dev/null
+++ b/accessible/mac/mozTextAccessible.h
@@ -0,0 +1,17 @@
+/* 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/. */
+
+#import "mozAccessible.h"
+
+#import "HyperTextAccessible.h"
+
+@interface mozTextAccessible : mozAccessible
+{
+}
+@end
+
+@interface mozTextLeafAccessible : mozAccessible
+{
+}
+@end
diff --git a/accessible/mac/mozTextAccessible.mm b/accessible/mac/mozTextAccessible.mm
new file mode 100644
index 000000000..0909cd512
--- /dev/null
+++ b/accessible/mac/mozTextAccessible.mm
@@ -0,0 +1,626 @@
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "Accessible-inl.h"
+#include "HyperTextAccessible-inl.h"
+#include "TextLeafAccessible.h"
+
+#include "nsCocoaUtils.h"
+#include "nsObjCExceptions.h"
+
+#import "mozTextAccessible.h"
+
+using namespace mozilla::a11y;
+
+inline bool
+ToNSRange(id aValue, NSRange* aRange)
+{
+ NS_PRECONDITION(aRange, "aRange is nil");
+
+ if ([aValue isKindOfClass:[NSValue class]] &&
+ strcmp([(NSValue*)aValue objCType], @encode(NSRange)) == 0) {
+ *aRange = [aValue rangeValue];
+ return true;
+ }
+
+ return false;
+}
+
+inline NSString*
+ToNSString(id aValue)
+{
+ if ([aValue isKindOfClass:[NSString class]]) {
+ return aValue;
+ }
+
+ return nil;
+}
+
+@interface mozTextAccessible ()
+- (NSString*)subrole;
+- (NSString*)selectedText;
+- (NSValue*)selectedTextRange;
+- (NSValue*)visibleCharacterRange;
+- (long)textLength;
+- (BOOL)isReadOnly;
+- (NSNumber*)caretLineNumber;
+- (void)setText:(NSString*)newText;
+- (NSString*)text;
+- (NSString*)stringFromRange:(NSRange*)range;
+@end
+
+@implementation mozTextAccessible
+
+- (BOOL)accessibilityIsIgnored
+{
+ return ![self getGeckoAccessible] && ![self getProxyAccessible];
+}
+
+- (NSArray*)accessibilityAttributeNames
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ static NSMutableArray* supportedAttributes = nil;
+ if (!supportedAttributes) {
+ // text-specific attributes to supplement the standard one
+ supportedAttributes = [[NSMutableArray alloc] initWithObjects:
+ NSAccessibilitySelectedTextAttribute, // required
+ NSAccessibilitySelectedTextRangeAttribute, // required
+ NSAccessibilityNumberOfCharactersAttribute, // required
+ NSAccessibilityVisibleCharacterRangeAttribute, // required
+ NSAccessibilityInsertionPointLineNumberAttribute,
+ @"AXRequired",
+ @"AXInvalid",
+ nil
+ ];
+ [supportedAttributes addObjectsFromArray:[super accessibilityAttributeNames]];
+ }
+ return supportedAttributes;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)accessibilityAttributeValue:(NSString*)attribute
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ([attribute isEqualToString:NSAccessibilityNumberOfCharactersAttribute])
+ return [NSNumber numberWithInt:[self textLength]];
+
+ if ([attribute isEqualToString:NSAccessibilityInsertionPointLineNumberAttribute])
+ return [self caretLineNumber];
+
+ if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute])
+ return [self selectedTextRange];
+
+ if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute])
+ return [self selectedText];
+
+ if ([attribute isEqualToString:NSAccessibilityTitleAttribute])
+ return @"";
+
+ if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
+ // Apple's SpeechSynthesisServer expects AXValue to return an AXStaticText
+ // object's AXSelectedText attribute. See bug 674612 for details.
+ // Also if there is no selected text, we return the full text.
+ // See bug 369710 for details.
+ if ([[self role] isEqualToString:NSAccessibilityStaticTextRole]) {
+ NSString* selectedText = [self selectedText];
+ return (selectedText && [selectedText length]) ? selectedText : [self text];
+ }
+
+ return [self text];
+ }
+
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ if ([attribute isEqualToString:@"AXRequired"]) {
+ return [NSNumber numberWithBool:!!(accWrap->State() & states::REQUIRED)];
+ }
+
+ if ([attribute isEqualToString:@"AXInvalid"]) {
+ return [NSNumber numberWithBool:!!(accWrap->State() & states::INVALID)];
+ }
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ if ([attribute isEqualToString:@"AXRequired"]) {
+ return [NSNumber numberWithBool:!!(proxy->State() & states::REQUIRED)];
+ }
+
+ if ([attribute isEqualToString:@"AXInvalid"]) {
+ return [NSNumber numberWithBool:!!(proxy->State() & states::INVALID)];
+ }
+ }
+
+ if ([attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute])
+ return [self visibleCharacterRange];
+
+ // let mozAccessible handle all other attributes
+ return [super accessibilityAttributeValue:attribute];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (NSArray*)accessibilityParameterizedAttributeNames
+{
+ static NSArray* supportedParametrizedAttributes = nil;
+ // text specific parametrized attributes
+ if (!supportedParametrizedAttributes) {
+ supportedParametrizedAttributes = [[NSArray alloc] initWithObjects:
+ NSAccessibilityStringForRangeParameterizedAttribute,
+ NSAccessibilityLineForIndexParameterizedAttribute,
+ NSAccessibilityRangeForLineParameterizedAttribute,
+ NSAccessibilityAttributedStringForRangeParameterizedAttribute,
+ NSAccessibilityBoundsForRangeParameterizedAttribute,
+#if DEBUG
+ NSAccessibilityRangeForPositionParameterizedAttribute,
+ NSAccessibilityRangeForIndexParameterizedAttribute,
+ NSAccessibilityRTFForRangeParameterizedAttribute,
+ NSAccessibilityStyleRangeForIndexParameterizedAttribute,
+#endif
+ nil
+ ];
+ }
+ return supportedParametrizedAttributes;
+}
+
+- (id)accessibilityAttributeValue:(NSString*)attribute forParameter:(id)parameter
+{
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+
+ HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
+ if (!textAcc && !proxy)
+ return nil;
+
+ if ([attribute isEqualToString:NSAccessibilityStringForRangeParameterizedAttribute]) {
+ NSRange range;
+ if (!ToNSRange(parameter, &range)) {
+#if DEBUG
+ NSLog(@"%@: range not set", attribute);
+#endif
+ return @"";
+ }
+
+ return [self stringFromRange:&range];
+ }
+
+ if ([attribute isEqualToString:NSAccessibilityRangeForLineParameterizedAttribute]) {
+ // XXX: actually get the integer value for the line #
+ return [NSValue valueWithRange:NSMakeRange(0, [self textLength])];
+ }
+
+ if ([attribute isEqualToString:NSAccessibilityAttributedStringForRangeParameterizedAttribute]) {
+ NSRange range;
+ if (!ToNSRange(parameter, &range)) {
+#if DEBUG
+ NSLog(@"%@: range not set", attribute);
+#endif
+ return @"";
+ }
+
+ return [[[NSAttributedString alloc] initWithString:[self stringFromRange:&range]] autorelease];
+ }
+
+ if ([attribute isEqualToString:NSAccessibilityLineForIndexParameterizedAttribute]) {
+ // XXX: actually return the line #
+ return [NSNumber numberWithInt:0];
+ }
+
+ if ([attribute isEqualToString:NSAccessibilityBoundsForRangeParameterizedAttribute]) {
+ NSRange range;
+ if (!ToNSRange(parameter, &range)) {
+#if DEBUG
+ NSLog(@"%@:no range", attribute);
+#endif
+ return nil;
+ }
+
+ int32_t start = range.location;
+ int32_t end = start + range.length;
+ DesktopIntRect bounds;
+ if (textAcc) {
+ bounds =
+ DesktopIntRect::FromUnknownRect(textAcc->TextBounds(start, end));
+ } else if (proxy) {
+ bounds =
+ DesktopIntRect::FromUnknownRect(proxy->TextBounds(start, end));
+ }
+
+ return [NSValue valueWithRect:nsCocoaUtils::GeckoRectToCocoaRect(bounds)];
+ }
+
+#if DEBUG
+ NSLog(@"unhandled attribute:%@ forParameter:%@", attribute, parameter);
+#endif
+
+ return nil;
+}
+
+- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if ([attribute isEqualToString:NSAccessibilityValueAttribute])
+ return ![self isReadOnly];
+
+ if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute] ||
+ [attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute] ||
+ [attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute])
+ return YES;
+
+ return [super accessibilityIsAttributeSettable:attribute];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+- (void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+
+ HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
+ if (!textAcc && !proxy)
+ return;
+
+ if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
+ [self setText:ToNSString(value)];
+
+ return;
+ }
+
+ if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) {
+ NSString* stringValue = ToNSString(value);
+ if (!stringValue)
+ return;
+
+ int32_t start = 0, end = 0;
+ nsString text;
+ if (textAcc) {
+ textAcc->SelectionBoundsAt(0, &start, &end);
+ textAcc->DeleteText(start, end - start);
+ nsCocoaUtils::GetStringForNSString(stringValue, text);
+ textAcc->InsertText(text, start);
+ } else if (proxy) {
+ nsString data;
+ proxy->SelectionBoundsAt(0, data, &start, &end);
+ proxy->DeleteText(start, end - start);
+ nsCocoaUtils::GetStringForNSString(stringValue, text);
+ proxy->InsertText(text, start);
+ }
+ }
+
+ if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
+ NSRange range;
+ if (!ToNSRange(value, &range))
+ return;
+
+ if (textAcc) {
+ textAcc->SetSelectionBoundsAt(0, range.location,
+ range.location + range.length);
+ } else if (proxy) {
+ proxy->SetSelectionBoundsAt(0, range.location,
+ range.location + range.length);
+ }
+ return;
+ }
+
+ if ([attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute]) {
+ NSRange range;
+ if (!ToNSRange(value, &range))
+ return;
+
+ if (textAcc) {
+ textAcc->ScrollSubstringTo(range.location, range.location + range.length,
+ nsIAccessibleScrollType::SCROLL_TYPE_TOP_EDGE);
+ } else if (proxy) {
+ proxy->ScrollSubstringTo(range.location, range.location + range.length,
+ nsIAccessibleScrollType::SCROLL_TYPE_TOP_EDGE);
+ }
+ return;
+ }
+
+ [super accessibilitySetValue:value forAttribute:attribute];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (NSString*)subrole
+{
+ if(mRole == roles::PASSWORD_TEXT)
+ return NSAccessibilitySecureTextFieldSubrole;
+
+ return nil;
+}
+
+#pragma mark -
+
+- (BOOL)isReadOnly
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if ([[self role] isEqualToString:NSAccessibilityStaticTextRole])
+ return YES;
+
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
+ if (textAcc)
+ return (accWrap->State() & states::READONLY) == 0;
+
+ if (ProxyAccessible* proxy = [self getProxyAccessible])
+ return (proxy->State() & states::READONLY) == 0;
+
+ return NO;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+- (NSNumber*)caretLineNumber
+{
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
+
+ int32_t lineNumber = -1;
+ if (textAcc) {
+ lineNumber = textAcc->CaretLineNumber() - 1;
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ lineNumber = proxy->CaretLineNumber() - 1;
+ }
+
+ return (lineNumber >= 0) ? [NSNumber numberWithInt:lineNumber] : nil;
+}
+
+- (void)setText:(NSString*)aNewString
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
+
+ nsString text;
+ nsCocoaUtils::GetStringForNSString(aNewString, text);
+ if (textAcc) {
+ textAcc->ReplaceText(text);
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ proxy->ReplaceText(text);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (NSString*)text
+{
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
+ if (!textAcc && !proxy)
+ return nil;
+
+ // A password text field returns an empty value
+ if (mRole == roles::PASSWORD_TEXT)
+ return @"";
+
+ nsAutoString text;
+ if (textAcc) {
+ textAcc->TextSubstring(0, nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT, text);
+ } else if (proxy) {
+ proxy->TextSubstring(0, nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT, text);
+ }
+
+ return nsCocoaUtils::ToNSString(text);
+}
+
+- (long)textLength
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
+ if (!textAcc && !proxy)
+ return 0;
+
+ return textAcc ? textAcc->CharacterCount() : proxy->CharacterCount();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+- (long)selectedTextLength
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
+ if (!textAcc && !proxy)
+ return 0;
+
+ int32_t start = 0, end = 0;
+ if (textAcc) {
+ textAcc->SelectionBoundsAt(0, &start, &end);
+ } else if (proxy) {
+ nsString data;
+ proxy->SelectionBoundsAt(0, data, &start, &end);
+ }
+ return (end - start);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+- (NSString*)selectedText
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
+ if (!textAcc && !proxy)
+ return nil;
+
+ int32_t start = 0, end = 0;
+ nsAutoString selText;
+ if (textAcc) {
+ textAcc->SelectionBoundsAt(0, &start, &end);
+ if (start != end) {
+ textAcc->TextSubstring(start, end, selText);
+ }
+ } else if (proxy) {
+ proxy->SelectionBoundsAt(0, selText, &start, &end);
+ }
+
+ return nsCocoaUtils::ToNSString(selText);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (NSValue*)selectedTextRange
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
+
+ int32_t start = 0;
+ int32_t end = 0;
+ int32_t count = 0;
+ if (textAcc) {
+ count = textAcc->SelectionCount();
+ if (count) {
+ textAcc->SelectionBoundsAt(0, &start, &end);
+ return [NSValue valueWithRange:NSMakeRange(start, end - start)];
+ }
+
+ start = textAcc->CaretOffset();
+ return [NSValue valueWithRange:NSMakeRange(start != -1 ? start : 0, 0)];
+ }
+
+ if (proxy) {
+ count = proxy->SelectionCount();
+ if (count) {
+ nsString data;
+ proxy->SelectionBoundsAt(0, data, &start, &end);
+ return [NSValue valueWithRange:NSMakeRange(start, end - start)];
+ }
+
+ start = proxy->CaretOffset();
+ return [NSValue valueWithRange:NSMakeRange(start != -1 ? start : 0, 0)];
+ }
+
+ return [NSValue valueWithRange:NSMakeRange(0, 0)];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (NSValue*)visibleCharacterRange
+{
+ // XXX this won't work with Textarea and such as we actually don't give
+ // the visible character range.
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
+ if (!textAcc && !proxy)
+ return 0;
+
+ return [NSValue valueWithRange:
+ NSMakeRange(0, textAcc ?
+ textAcc->CharacterCount() : proxy->CharacterCount())];
+}
+
+- (void)valueDidChange
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSAccessibilityPostNotification(GetObjectOrRepresentedView(self),
+ NSAccessibilityValueChangedNotification);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)selectedTextDidChange
+{
+ NSAccessibilityPostNotification(GetObjectOrRepresentedView(self),
+ NSAccessibilitySelectedTextChangedNotification);
+}
+
+- (NSString*)stringFromRange:(NSRange*)range
+{
+ NS_PRECONDITION(range, "no range");
+
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
+ if (!textAcc && !proxy)
+ return nil;
+
+ nsAutoString text;
+ if (textAcc) {
+ textAcc->TextSubstring(range->location,
+ range->location + range->length, text);
+ } else if (proxy) {
+ proxy->TextSubstring(range->location,
+ range->location + range->length, text);
+ }
+
+ return nsCocoaUtils::ToNSString(text);
+}
+
+@end
+
+@implementation mozTextLeafAccessible
+
+- (NSArray*)accessibilityAttributeNames
+{
+ static NSMutableArray* supportedAttributes = nil;
+ if (!supportedAttributes) {
+ supportedAttributes = [[super accessibilityAttributeNames] mutableCopy];
+ [supportedAttributes removeObject:NSAccessibilityChildrenAttribute];
+ }
+
+ return supportedAttributes;
+}
+
+- (id)accessibilityAttributeValue:(NSString*)attribute
+{
+ if ([attribute isEqualToString:NSAccessibilityTitleAttribute])
+ return @"";
+
+ if ([attribute isEqualToString:NSAccessibilityValueAttribute])
+ return [self text];
+
+ return [super accessibilityAttributeValue:attribute];
+}
+
+- (NSString*)text
+{
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ return nsCocoaUtils::ToNSString(accWrap->AsTextLeaf()->Text());
+ }
+
+ if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ nsString text;
+ proxy->Text(&text);
+ return nsCocoaUtils::ToNSString(text);
+ }
+
+ return nil;
+}
+
+- (long)textLength
+{
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ return accWrap->AsTextLeaf()->Text().Length();
+ }
+
+ if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ nsString text;
+ proxy->Text(&text);
+ return text.Length();
+ }
+
+ return 0;
+}
+
+@end
diff --git a/accessible/moz.build b/accessible/moz.build
new file mode 100644
index 000000000..48641770d
--- /dev/null
+++ b/accessible/moz.build
@@ -0,0 +1,36 @@
+# -*- 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/.
+
+toolkit = CONFIG['MOZ_WIDGET_TOOLKIT']
+
+if 'gtk' in toolkit:
+ DIRS += ['atk']
+elif toolkit == 'windows':
+ DIRS += ['windows']
+elif toolkit == 'cocoa':
+ DIRS += ['mac']
+else:
+ DIRS += ['other']
+
+DIRS += [ 'aom',
+ 'base',
+ 'generic',
+ 'html',
+ 'interfaces',
+ 'ipc',
+ 'jsat',
+ 'xpcom'
+]
+
+if CONFIG['MOZ_XUL']:
+ DIRS += ['xul']
+
+TEST_DIRS += ['tests/mochitest']
+
+BROWSER_CHROME_MANIFESTS += [
+ 'tests/browser/browser.ini',
+ 'tests/browser/e10s/browser.ini'
+]
diff --git a/accessible/other/ARIAGridAccessibleWrap.h b/accessible/other/ARIAGridAccessibleWrap.h
new file mode 100644
index 000000000..89c2137e4
--- /dev/null
+++ b/accessible/other/ARIAGridAccessibleWrap.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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_ARIAGRIDACCESSIBLEWRAP_H
+#define MOZILLA_A11Y_ARIAGRIDACCESSIBLEWRAP_H
+
+#include "ARIAGridAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class ARIAGridAccessible ARIAGridAccessibleWrap;
+typedef class ARIAGridCellAccessible ARIAGridCellAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/other/AccessibleWrap.cpp b/accessible/other/AccessibleWrap.cpp
new file mode 100644
index 000000000..9ea33ce74
--- /dev/null
+++ b/accessible/other/AccessibleWrap.cpp
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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"
+
+using namespace mozilla::a11y;
+
+//-----------------------------------------------------
+// construction
+//-----------------------------------------------------
+AccessibleWrap::
+ AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) :
+ Accessible(aContent, aDoc)
+{
+}
+
+//-----------------------------------------------------
+// destruction
+//-----------------------------------------------------
+AccessibleWrap::~AccessibleWrap()
+{
+}
+
diff --git a/accessible/other/AccessibleWrap.h b/accessible/other/AccessibleWrap.h
new file mode 100644
index 000000000..ea6ac6d40
--- /dev/null
+++ b/accessible/other/AccessibleWrap.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_AccessibleWrap_h_
+#define mozilla_a11y_AccessibleWrap_h_
+
+#include "nsCOMPtr.h"
+#include "Accessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+class AccessibleWrap : public Accessible
+{
+public: // construction, destruction
+ AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc);
+ virtual ~AccessibleWrap();
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/other/ApplicationAccessibleWrap.h b/accessible/other/ApplicationAccessibleWrap.h
new file mode 100644
index 000000000..440410df2
--- /dev/null
+++ b/accessible/other/ApplicationAccessibleWrap.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* 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 {
+
+typedef ApplicationAccessible ApplicationAccessibleWrap;
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/other/DocAccessibleWrap.h b/accessible/other/DocAccessibleWrap.h
new file mode 100644
index 000000000..ba9b6ac18
--- /dev/null
+++ b/accessible/other/DocAccessibleWrap.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 {
+namespace a11y {
+
+typedef DocAccessible DocAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/other/HTMLTableAccessibleWrap.h b/accessible/other/HTMLTableAccessibleWrap.h
new file mode 100644
index 000000000..4f158e241
--- /dev/null
+++ b/accessible/other/HTMLTableAccessibleWrap.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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_HTMLTableAccessibleWrap_h__
+#define mozilla_a11y_HTMLTableAccessibleWrap_h__
+
+#include "HTMLTableAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class HTMLTableAccessible HTMLTableAccessibleWrap;
+typedef class HTMLTableCellAccessible HTMLTableCellAccessibleWrap;
+typedef class HTMLTableHeaderCellAccessible HTMLTableHeaderCellAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/other/HyperTextAccessibleWrap.h b/accessible/other/HyperTextAccessibleWrap.h
new file mode 100644
index 000000000..fb335ef0f
--- /dev/null
+++ b/accessible/other/HyperTextAccessibleWrap.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_HyperTextAccessibleWrap_h__
+#define mozilla_a11y_HyperTextAccessibleWrap_h__
+
+#include "HyperTextAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class HyperTextAccessible HyperTextAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/other/ImageAccessibleWrap.h b/accessible/other/ImageAccessibleWrap.h
new file mode 100644
index 000000000..069efb651
--- /dev/null
+++ b/accessible/other/ImageAccessibleWrap.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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_ImageAccessibleWrap_h__
+#define mozilla_a11y_ImageAccessibleWrap_h__
+
+#include "ImageAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class ImageAccessible ImageAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/other/Platform.cpp b/accessible/other/Platform.cpp
new file mode 100644
index 000000000..6f4527a78
--- /dev/null
+++ b/accessible/other/Platform.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 "Platform.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+void
+a11y::PlatformInit()
+{
+}
+
+void
+a11y::PlatformShutdown()
+{
+}
+
+void
+a11y::ProxyCreated(ProxyAccessible*, uint32_t)
+{
+}
+
+void
+a11y::ProxyDestroyed(ProxyAccessible*)
+{
+}
+
+void
+a11y::ProxyEvent(ProxyAccessible*, uint32_t)
+{
+}
+
+void
+a11y::ProxyStateChangeEvent(ProxyAccessible*, uint64_t, bool)
+{
+}
+
+void
+a11y::ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset)
+{
+}
+
+void
+a11y::ProxyTextChangeEvent(ProxyAccessible*, const nsString&, int32_t, uint32_t,
+ bool, bool)
+{
+}
+
+void
+a11y::ProxyShowHideEvent(ProxyAccessible*, ProxyAccessible*, bool, bool)
+{
+}
+
+void
+a11y::ProxySelectionEvent(ProxyAccessible*, ProxyAccessible*, uint32_t)
+{
+}
diff --git a/accessible/other/RootAccessibleWrap.h b/accessible/other/RootAccessibleWrap.h
new file mode 100644
index 000000000..62bda99eb
--- /dev/null
+++ b/accessible/other/RootAccessibleWrap.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_RootAccessibleWrap_h__
+#define mozilla_a11y_RootAccessibleWrap_h__
+
+#include "RootAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef RootAccessible RootAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/other/TextLeafAccessibleWrap.h b/accessible/other/TextLeafAccessibleWrap.h
new file mode 100644
index 000000000..93f3b5148
--- /dev/null
+++ b/accessible/other/TextLeafAccessibleWrap.h
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_TextLeafAccessibleWrap_h__
+#define mozilla_a11y_TextLeafAccessibleWrap_h__
+
+#include "TextLeafAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class TextLeafAccessible TextLeafAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/other/XULListboxAccessibleWrap.h b/accessible/other/XULListboxAccessibleWrap.h
new file mode 100644
index 000000000..f7dc6cc54
--- /dev/null
+++ b/accessible/other/XULListboxAccessibleWrap.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_XULListboxAccessibleWrap_h__
+#define mozilla_a11y_XULListboxAccessibleWrap_h__
+
+#include "XULListboxAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class XULListboxAccessible XULListboxAccessibleWrap;
+typedef class XULListCellAccessible XULListCellAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/other/XULMenuAccessibleWrap.h b/accessible/other/XULMenuAccessibleWrap.h
new file mode 100644
index 000000000..6efcf007e
--- /dev/null
+++ b/accessible/other/XULMenuAccessibleWrap.h
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_XULMenuAccessibleWrap_h__
+#define mozilla_a11y_XULMenuAccessibleWrap_h__
+
+#include "XULMenuAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class XULMenuitemAccessible XULMenuitemAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/other/XULTreeGridAccessibleWrap.h b/accessible/other/XULTreeGridAccessibleWrap.h
new file mode 100644
index 000000000..b3631e9ad
--- /dev/null
+++ b/accessible/other/XULTreeGridAccessibleWrap.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_XULTreeGridAccessibleWrap_h__
+#define mozilla_a11y_XULTreeGridAccessibleWrap_h__
+
+#include "XULTreeGridAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class XULTreeGridAccessible XULTreeGridAccessibleWrap;
+typedef class XULTreeGridCellAccessible XULTreeGridCellAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/other/moz.build b/accessible/other/moz.build
new file mode 100644
index 000000000..a3ad2fc19
--- /dev/null
+++ b/accessible/other/moz.build
@@ -0,0 +1,27 @@
+# -*- 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',
+ 'HyperTextAccessibleWrap.h',
+]
+
+SOURCES += [
+ 'AccessibleWrap.cpp',
+ 'Platform.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/accessible/base',
+ '/accessible/generic',
+ '/accessible/html',
+ '/accessible/xul',
+]
+
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/accessible/tests/browser/.eslintrc.js b/accessible/tests/browser/.eslintrc.js
new file mode 100644
index 000000000..53425abc4
--- /dev/null
+++ b/accessible/tests/browser/.eslintrc.js
@@ -0,0 +1,218 @@
+"use strict";
+
+module.exports = { // eslint-disable-line no-undef
+ "extends": [
+ "../../../testing/mochitest/browser.eslintrc.js"
+ ],
+ // All globals made available in the test environment.
+ "globals": {
+ // Content scripts have global 'content' object
+ "content": true,
+
+ "add_task": true,
+
+ // Defined in accessible/tests/mochitest/ common.js, name.js, states.js
+ "prettyName": true,
+ "statesToString": true,
+ "eventTypeToString": true,
+ "testAttrs": true,
+ "testAbsentAttrs": true,
+ "testName": true,
+ "testDescr": true,
+ "testStates": true,
+ "testRelation": true,
+ "testValue": true,
+ "testAccessibleTree": true,
+ "isAccessible": true,
+ "getAccessibleDOMNodeID": true,
+
+ // Defined for all top level accessibility browser tests.
+ "setE10sPrefs": true,
+ "unsetE10sPrefs": true,
+ "initPromise": true,
+ "shutdownPromise": true,
+ "forceGC": true,
+
+ // Defined for all e10s accessibility browser tests.
+ "addAccessibleTask": true,
+ "BrowserTestUtils": true,
+ "ContentTask": true,
+ "gBrowser": true,
+ "isDefunct": true,
+ "loadScripts": true,
+ "loadFrameScripts": true,
+ "Logger": true,
+ "MOCHITESTS_DIR": true,
+ "waitForEvent": true,
+ "waitForMultipleEvents": true,
+ "invokeSetAttribute": true,
+ "invokeSetStyle": true,
+ "invokeFocus": true,
+ "findAccessibleChildByID": true
+ },
+ "rules": {
+ "mozilla/no-aArgs": "warn",
+ "mozilla/no-cpows-in-tests": "warn",
+ "mozilla/reject-importGlobalProperties": "warn",
+ "mozilla/var-only-at-top-level": "warn",
+
+ "block-scoped-var": "error",
+ "brace-style": ["error", "1tbs"],
+ "camelcase": "error",
+ "comma-dangle": ["error", "never"],
+ "comma-spacing": "error",
+ "comma-style": ["error", "last"],
+ "complexity": ["error", 35],
+ "consistent-this": "off",
+ "curly": ["error", "multi-line"],
+ "default-case": "off",
+ "dot-location": ["error", "property"],
+ "dot-notation": "error",
+ "eol-last": "error",
+ "eqeqeq": "off",
+ "func-names": "off",
+ "func-style": "off",
+ "generator-star": "off",
+ "global-strict": "off",
+ "handle-callback-err": ["error", "er"],
+ "indent": ["error", 2, {"SwitchCase": 1}],
+ "key-spacing": ["error", {"beforeColon": false, "afterColon": true}],
+ "linebreak-style": "off",
+ "max-depth": "off",
+ "max-nested-callbacks": ["error", 4],
+ "max-params": "off",
+ "max-statements": "off",
+ "new-cap": ["error", {"capIsNew": false}],
+ "new-parens": "error",
+ "no-array-constructor": "error",
+ "no-bitwise": "off",
+ "no-caller": "error",
+ "no-catch-shadow": "error",
+ "no-comma-dangle": "off",
+ "no-cond-assign": "error",
+ "no-console": "off",
+ "no-constant-condition": "off",
+ "no-continue": "off",
+ "no-control-regex": "error",
+ "no-debugger": "error",
+ "no-delete-var": "error",
+ "no-div-regex": "off",
+ "no-dupe-args": "error",
+ "no-dupe-keys": "error",
+ "no-duplicate-case": "error",
+ "no-else-return": "error",
+ "no-empty": "error",
+ "no-empty-character-class": "error",
+ "no-eval": "error",
+ "no-ex-assign": "error",
+ "no-extend-native": "error",
+ "no-extra-bind": "error",
+ "no-extra-boolean-cast": "error",
+ "no-extra-parens": "off",
+ "no-extra-semi": "error",
+ "no-extra-strict": "off",
+ "no-fallthrough": "error",
+ "no-floating-decimal": "off",
+ "no-inline-comments": "off",
+ "no-lonely-if": "error",
+ "no-mixed-requires": "off",
+ "no-mixed-spaces-and-tabs": "error",
+ "no-multi-spaces": "error",
+ "no-multi-str": "error",
+ "no-multiple-empty-lines": ["error", {"max": 1}],
+ "no-native-reassign": "error",
+ "no-nested-ternary": "error",
+ "no-new-require": "off",
+ "no-octal": "error",
+ "no-param-reassign": "off",
+ "no-path-concat": "off",
+ "no-plusplus": "off",
+ "no-process-env": "off",
+ "no-process-exit": "off",
+ "no-proto": "error",
+ "no-redeclare": "error",
+ "no-regex-spaces": "error",
+ "no-reserved-keys": "off",
+ "no-restricted-modules": "off",
+ "no-return-assign": "error",
+ "no-script-url": "off",
+ "no-self-compare": "error",
+ "no-sequences": "error",
+ "no-shadow": "error",
+ "no-shadow-restricted-names": "error",
+ "no-space-before-semi": "off",
+ "no-spaced-func": "error",
+ "no-sparse-arrays": "error",
+ "no-sync": "off",
+ "no-ternary": "off",
+ "no-throw-literal": "error",
+ "no-trailing-spaces": "error",
+ "no-undef": "error",
+ "no-underscore-dangle": "off",
+ "no-undefined": "off",
+ "no-unneeded-ternary": "error",
+ "no-unreachable": "error",
+ "no-unused-vars": ["error", {"vars": "all", "args": "none"}],
+ "no-use-before-define": "off",
+ "no-var": "off",
+ "no-warning-comments": "off",
+ "no-with": "error",
+ "object-shorthand": "off",
+ "one-var": ["error", "never"],
+ "padded-blocks": ["error", "never"],
+ "quote-props": "off",
+ "radix": "error",
+ "semi": ["error", "always"],
+ "semi-spacing": ["error", {"before": false, "after": true}],
+ "sort-vars": "off",
+ "space-after-function-name": "off",
+ "keyword-spacing": "error",
+ "space-before-blocks": "error",
+ "space-before-function-parentheses": "off",
+ "space-before-function-paren": ["error", "never"],
+ "space-in-brackets": "off",
+ "space-in-parens": ["error", "never"],
+ "space-infix-ops": ["error", {"int32Hint": true}],
+ "space-unary-ops": ["error", { "words": true, "nonwords": false }],
+ "space-unary-word-ops": "off",
+ "spaced-comment": ["error", "always"],
+ "strict": ["error", "global"],
+ "use-isnan": "error",
+ "valid-jsdoc": "off",
+ "valid-typeof": "error",
+ "vars-on-top": "off",
+ "wrap-iife": "off",
+ "wrap-regex": "off",
+ "yoda": "error",
+
+ "guard-for-in": "off",
+ "newline-after-var": "off",
+ "no-alert": "off",
+ "no-eq-null": "off",
+ "no-func-assign": "off",
+ "no-implied-eval": "off",
+ "no-inner-declarations": "off",
+ "no-invalid-regexp": "off",
+ "no-irregular-whitespace": "off",
+ "no-iterator": "off",
+ "no-label-var": "off",
+ "no-labels": "error",
+ "no-lone-blocks": "off",
+ "no-loop-func": "off",
+ "no-negated-in-lhs": "off",
+ "no-new": "off",
+ "no-new-func": "off",
+ "no-new-object": "off",
+ "no-new-wrappers": "off",
+ "no-obj-calls": "off",
+ "no-octal-escape": "off",
+ "no-undef-init": "error",
+ "no-unexpected-multiline": "error",
+ "object-curly-spacing": "off",
+ "no-unused-expressions": "off",
+ "no-void": "off",
+ "no-wrap-func": "off",
+ "operator-assignment": "off",
+ "operator-linebreak": ["error", "after"]
+ }
+};
diff --git a/accessible/tests/browser/browser.ini b/accessible/tests/browser/browser.ini
new file mode 100644
index 000000000..402deda24
--- /dev/null
+++ b/accessible/tests/browser/browser.ini
@@ -0,0 +1,17 @@
+[DEFAULT]
+
+support-files =
+ head.js
+ shared-head.js
+
+[browser_shutdown_multi_reference.js]
+[browser_shutdown_parent_own_reference.js]
+skip-if = !e10s # e10s specific test for a11y start/shutdown between parent and content.
+[browser_shutdown_remote_no_reference.js]
+skip-if = !e10s # e10s specific test for a11y start/shutdown between parent and content.
+[browser_shutdown_remote_only.js]
+skip-if = !e10s # e10s specific test for a11y start/shutdown between parent and content.
+[browser_shutdown_remote_own_reference.js]
+skip-if = !e10s # e10s specific test for a11y start/shutdown between parent and content.
+[browser_shutdown_scope_lifecycle.js]
+[browser_shutdown_start_restart.js]
diff --git a/accessible/tests/browser/browser_shutdown_multi_reference.js b/accessible/tests/browser/browser_shutdown_multi_reference.js
new file mode 100644
index 000000000..e0ba3ce6b
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_multi_reference.js
@@ -0,0 +1,48 @@
+/* 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/. */
+
+'use strict';
+
+add_task(function* () {
+ info('Creating a service');
+ // Create a11y service.
+ let a11yInit = initPromise();
+ let accService1 = Cc['@mozilla.org/accessibilityService;1'].getService(
+ Ci.nsIAccessibilityService);
+ yield a11yInit;
+ ok(accService1, 'Service initialized');
+
+ // Add another reference to a11y service. This will not trigger
+ // 'a11y-init-or-shutdown' event
+ let accService2 = Cc['@mozilla.org/accessibilityService;1'].getService(
+ Ci.nsIAccessibilityService);
+ ok(accService2, 'Service initialized');
+
+ info('Removing all service references');
+ let canShutdown = false;
+ // This promise will resolve only if canShutdonw flag is set to true. If
+ // 'a11y-init-or-shutdown' event with '0' flag comes before it can be shut
+ // down, the promise will reject.
+ let a11yShutdown = new Promise((resolve, reject) =>
+ shutdownPromise().then(flag => canShutdown ?
+ resolve() : reject('Accessible service was shut down incorrectly')));
+ // Remove first a11y service reference.
+ accService1 = null;
+ ok(!accService1, 'Service is removed');
+ // Force garbage collection that should not trigger shutdown because there is
+ // another reference.
+ forceGC();
+
+ // Have some breathing room when removing a11y service references.
+ yield new Promise(resolve => executeSoon(resolve));
+
+ // Now allow a11y service to shutdown.
+ canShutdown = true;
+ // Remove last a11y service reference.
+ accService2 = null;
+ ok(!accService2, 'Service is removed');
+ // Force garbage collection that should trigger shutdown.
+ forceGC();
+ yield a11yShutdown;
+});
diff --git a/accessible/tests/browser/browser_shutdown_parent_own_reference.js b/accessible/tests/browser/browser_shutdown_parent_own_reference.js
new file mode 100644
index 000000000..9895bd2c7
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_parent_own_reference.js
@@ -0,0 +1,72 @@
+/* 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/. */
+
+'use strict';
+
+add_task(function* () {
+ // Making sure that the e10s is enabled on Windows for testing.
+ yield setE10sPrefs();
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: `data:text/html,
+ <html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Test</title>
+ </head>
+ <body></body>
+ </html>`
+ }, function*(browser) {
+ info('Creating a service in parent and waiting for service to be created ' +
+ 'in content');
+ // Create a11y service in the main process. This will trigger creating of
+ // the a11y service in parent as well.
+ let parentA11yInit = initPromise();
+ let contentA11yInit = initPromise(browser);
+ let accService = Cc['@mozilla.org/accessibilityService;1'].getService(
+ Ci.nsIAccessibilityService);
+ ok(accService, 'Service initialized in parent');
+ yield Promise.all([parentA11yInit, contentA11yInit]);
+
+ info('Adding additional reference to accessibility service in content ' +
+ 'process');
+ // Add a new reference to the a11y service inside the content process.
+ loadFrameScripts(browser, `let accService = Components.classes[
+ '@mozilla.org/accessibilityService;1'].getService(
+ Components.interfaces.nsIAccessibilityService);`);
+
+ info('Trying to shut down a service in content and making sure it stays ' +
+ 'alive as it was started by parent');
+ let contentCanShutdown = false;
+ // This promise will resolve only if contentCanShutdown flag is set to true.
+ // If 'a11y-init-or-shutdown' event with '0' flag (in content) comes before
+ // it can be shut down, the promise will reject.
+ let contentA11yShutdown = new Promise((resolve, reject) =>
+ shutdownPromise(browser).then(flag => contentCanShutdown ?
+ resolve() : reject('Accessible service was shut down incorrectly')));
+ // Remove a11y service reference in content and force garbage collection.
+ // This should not trigger shutdown since a11y was originally initialized by
+ // the main process.
+ loadFrameScripts(browser, `accService = null; Components.utils.forceGC();`);
+
+ // Have some breathing room between a11y service shutdowns.
+ yield new Promise(resolve => executeSoon(resolve));
+
+ info('Removing a service in parent');
+ // Now allow a11y service to shutdown in content.
+ contentCanShutdown = true;
+ // Remove the a11y service reference in the main process.
+ let parentA11yShutdown = shutdownPromise();
+ accService = null;
+ ok(!accService, 'Service is removed in parent');
+ // Force garbage collection that should trigger shutdown in both parent and
+ // content.
+ forceGC();
+ yield Promise.all([parentA11yShutdown, contentA11yShutdown]);
+
+ // Unsetting e10s related preferences.
+ yield unsetE10sPrefs();
+ });
+});
diff --git a/accessible/tests/browser/browser_shutdown_remote_no_reference.js b/accessible/tests/browser/browser_shutdown_remote_no_reference.js
new file mode 100644
index 000000000..b066c2592
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_remote_no_reference.js
@@ -0,0 +1,48 @@
+/* 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/. */
+
+'use strict';
+
+add_task(function* () {
+ // Making sure that the e10s is enabled on Windows for testing.
+ yield setE10sPrefs();
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: `data:text/html,
+ <html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Test</title>
+ </head>
+ <body></body>
+ </html>`
+ }, function*(browser) {
+ info('Creating a service in parent and waiting for service to be created ' +
+ 'in content');
+ // Create a11y service in the main process. This will trigger creating of
+ // the a11y service in parent as well.
+ let parentA11yInit = initPromise();
+ let contentA11yInit = initPromise(browser);
+ let accService = Cc['@mozilla.org/accessibilityService;1'].getService(
+ Ci.nsIAccessibilityService);
+ ok(accService, 'Service initialized in parent');
+ yield Promise.all([parentA11yInit, contentA11yInit]);
+
+ info('Removing a service in parent and waiting for service to be shut ' +
+ 'down in content');
+ // Remove a11y service reference in the main process.
+ let parentA11yShutdown = shutdownPromise();
+ let contentA11yShutdown = shutdownPromise(browser);
+ accService = null;
+ ok(!accService, 'Service is removed in parent');
+ // Force garbage collection that should trigger shutdown in both main and
+ // content process.
+ forceGC();
+ yield Promise.all([parentA11yShutdown, contentA11yShutdown]);
+ });
+
+ // Unsetting e10s related preferences.
+ yield unsetE10sPrefs();
+});
diff --git a/accessible/tests/browser/browser_shutdown_remote_only.js b/accessible/tests/browser/browser_shutdown_remote_only.js
new file mode 100644
index 000000000..aab497678
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_remote_only.js
@@ -0,0 +1,40 @@
+/* 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/. */
+
+'use strict';
+
+add_task(function* () {
+ // Making sure that the e10s is enabled on Windows for testing.
+ yield setE10sPrefs();
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: `data:text/html,
+ <html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Test</title>
+ </head>
+ <body></body>
+ </html>`
+ }, function*(browser) {
+ info('Creating a service in content');
+ // Create a11y service in the content process.
+ let a11yInit = initPromise(browser);
+ loadFrameScripts(browser, `let accService = Components.classes[
+ '@mozilla.org/accessibilityService;1'].getService(
+ Components.interfaces.nsIAccessibilityService);`);
+ yield a11yInit;
+
+ info('Removing a service in content');
+ // Remove a11y service reference from the content process.
+ let a11yShutdown = shutdownPromise(browser);
+ // Force garbage collection that should trigger shutdown.
+ loadFrameScripts(browser, `accService = null; Components.utils.forceGC();`);
+ yield a11yShutdown;
+
+ // Unsetting e10s related preferences.
+ yield unsetE10sPrefs();
+ });
+});
diff --git a/accessible/tests/browser/browser_shutdown_remote_own_reference.js b/accessible/tests/browser/browser_shutdown_remote_own_reference.js
new file mode 100644
index 000000000..429737a81
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_remote_own_reference.js
@@ -0,0 +1,75 @@
+/* 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/. */
+
+'use strict';
+
+add_task(function* () {
+ // Making sure that the e10s is enabled on Windows for testing.
+ yield setE10sPrefs();
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: `data:text/html,
+ <html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Test</title>
+ </head>
+ <body></body>
+ </html>`
+ }, function*(browser) {
+ info('Creating a service in parent and waiting for service to be created ' +
+ 'in content');
+ // Create a11y service in the main process. This will trigger creating of
+ // the a11y service in parent as well.
+ let parentA11yInit = initPromise();
+ let contentA11yInit = initPromise(browser);
+ let accService = Cc['@mozilla.org/accessibilityService;1'].getService(
+ Ci.nsIAccessibilityService);
+ ok(accService, 'Service initialized in parent');
+ yield Promise.all([parentA11yInit, contentA11yInit]);
+
+ info('Adding additional reference to accessibility service in content ' +
+ 'process');
+ // Add a new reference to the a11y service inside the content process.
+ loadFrameScripts(browser, `let accService = Components.classes[
+ '@mozilla.org/accessibilityService;1'].getService(
+ Components.interfaces.nsIAccessibilityService);`);
+
+ info('Shutting down a service in parent and making sure the one in ' +
+ 'content stays alive');
+ let contentCanShutdown = false;
+ let parentA11yShutdown = shutdownPromise();
+ // This promise will resolve only if contentCanShutdown flag is set to true.
+ // If 'a11y-init-or-shutdown' event with '0' flag (in content) comes before
+ // it can be shut down, the promise will reject.
+ let contentA11yShutdown = new Promise((resolve, reject) =>
+ shutdownPromise(browser).then(flag => contentCanShutdown ?
+ resolve() : reject('Accessible service was shut down incorrectly')));
+ // Remove a11y service reference in the main process and force garbage
+ // collection. This should not trigger shutdown in content since a11y
+ // service is used by XPCOM.
+ accService = null;
+ ok(!accService, 'Service is removed in parent');
+ // Force garbage collection that should not trigger shutdown because there
+ // is a reference in a content process.
+ forceGC();
+ loadFrameScripts(browser, `Components.utils.forceGC();`);
+ yield parentA11yShutdown;
+
+ // Have some breathing room between a11y service shutdowns.
+ yield new Promise(resolve => executeSoon(resolve));
+
+ info('Removing a service in content');
+ // Now allow a11y service to shutdown in content.
+ contentCanShutdown = true;
+ // Remove last reference to a11y service in content and force garbage
+ // collection that should trigger shutdown.
+ loadFrameScripts(browser, `accService = null; Components.utils.forceGC();`);
+ yield contentA11yShutdown;
+
+ // Unsetting e10s related preferences.
+ yield unsetE10sPrefs();
+ });
+});
diff --git a/accessible/tests/browser/browser_shutdown_scope_lifecycle.js b/accessible/tests/browser/browser_shutdown_scope_lifecycle.js
new file mode 100644
index 000000000..be9c63d46
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_scope_lifecycle.js
@@ -0,0 +1,21 @@
+/* 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/. */
+
+'use strict';
+
+add_task(function* () {
+ // Create a11y service inside of the function scope. Its reference should be
+ // released once the anonimous function is called.
+ let a11yInitThenShutdown = initPromise().then(shutdownPromise);
+
+ (function() {
+ let accService = Cc['@mozilla.org/accessibilityService;1'].getService(
+ Ci.nsIAccessibilityService);
+ ok(accService, 'Service initialized');
+ })();
+
+ // Force garbage collection that should trigger shutdown.
+ forceGC();
+ yield a11yInitThenShutdown;
+});
diff --git a/accessible/tests/browser/browser_shutdown_start_restart.js b/accessible/tests/browser/browser_shutdown_start_restart.js
new file mode 100644
index 000000000..53bd4daee
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_start_restart.js
@@ -0,0 +1,41 @@
+/* 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/. */
+
+'use strict';
+
+add_task(function* () {
+ info('Creating a service');
+ // Create a11y service.
+ let a11yInit = initPromise();
+ let accService = Cc['@mozilla.org/accessibilityService;1'].getService(
+ Ci.nsIAccessibilityService);
+ yield a11yInit;
+ ok(accService, 'Service initialized');
+
+ info('Removing a service');
+ // Remove the only reference to an a11y service.
+ let a11yShutdown = shutdownPromise();
+ accService = null;
+ ok(!accService, 'Service is removed');
+ // Force garbage collection that should trigger shutdown.
+ forceGC();
+ yield a11yShutdown;
+
+ info('Recreating a service');
+ // Re-create a11y service.
+ a11yInit = initPromise();
+ accService = Cc['@mozilla.org/accessibilityService;1'].getService(
+ Ci.nsIAccessibilityService);
+ yield a11yInit;
+ ok(accService, 'Service initialized again');
+
+ info('Removing a service again');
+ // Remove the only reference to an a11y service again.
+ a11yShutdown = shutdownPromise();
+ accService = null;
+ ok(!accService, 'Service is removed again');
+ // Force garbage collection that should trigger shutdown.
+ forceGC();
+ yield a11yShutdown;
+});
diff --git a/accessible/tests/browser/e10s/browser.ini b/accessible/tests/browser/e10s/browser.ini
new file mode 100644
index 000000000..e0ca44dde
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser.ini
@@ -0,0 +1,51 @@
+[DEFAULT]
+skip-if = (e10s && os == 'win') # Bug 1269369: Document loaded event does not fire in Windows
+support-files =
+ events.js
+ head.js
+ doc_treeupdate_ariadialog.html
+ doc_treeupdate_ariaowns.html
+ doc_treeupdate_imagemap.html
+ doc_treeupdate_removal.xhtml
+ doc_treeupdate_visibility.html
+ doc_treeupdate_whitespace.html
+ !/accessible/tests/browser/shared-head.js
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/letters.gif
+ !/accessible/tests/mochitest/moz.png
+
+# Caching tests
+[browser_caching_attributes.js]
+[browser_caching_description.js]
+[browser_caching_name.js]
+[browser_caching_relations.js]
+[browser_caching_states.js]
+[browser_caching_value.js]
+
+# Events tests
+[browser_events_caretmove.js]
+[browser_events_hide.js]
+[browser_events_show.js]
+[browser_events_statechange.js]
+[browser_events_textchange.js]
+
+# Tree update tests
+[browser_treeupdate_ariadialog.js]
+[browser_treeupdate_ariaowns.js]
+[browser_treeupdate_canvas.js]
+[browser_treeupdate_cssoverflow.js]
+[browser_treeupdate_doc.js]
+[browser_treeupdate_gencontent.js]
+[browser_treeupdate_hidden.js]
+[browser_treeupdate_imagemap.js]
+skip-if = e10s # Bug 1318569
+[browser_treeupdate_list.js]
+[browser_treeupdate_list_editabledoc.js]
+[browser_treeupdate_listener.js]
+[browser_treeupdate_optgroup.js]
+[browser_treeupdate_removal.js]
+[browser_treeupdate_table.js]
+[browser_treeupdate_textleaf.js]
+[browser_treeupdate_visibility.js]
+[browser_treeupdate_whitespace.js]
+skip-if = true # Failing due to incorrect index of test container children on document load.
diff --git a/accessible/tests/browser/e10s/browser_caching_attributes.js b/accessible/tests/browser/e10s/browser_caching_attributes.js
new file mode 100644
index 000000000..449ca9d91
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_attributes.js
@@ -0,0 +1,117 @@
+/* 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/. */
+
+'use strict';
+
+/* global EVENT_FOCUS */
+
+loadScripts({ name: 'attributes.js', dir: MOCHITESTS_DIR });
+
+/**
+ * Default textbox accessible attributes.
+ */
+const defaultAttributes = {
+ 'margin-top': '0px',
+ 'margin-right': '0px',
+ 'margin-bottom': '0px',
+ 'margin-left': '0px',
+ 'text-align': 'start',
+ 'text-indent': '0px',
+ 'id': 'textbox',
+ 'tag': 'input',
+ 'display': 'inline'
+};
+
+/**
+ * Test data has the format of:
+ * {
+ * desc {String} description for better logging
+ * expected {Object} expected attributes for given accessibles
+ * unexpected {Object} unexpected attributes for given accessibles
+ *
+ * action {?Function*} an optional action that yields a change in
+ * attributes
+ * attrs {?Array} an optional list of attributes to update
+ * waitFor {?Number} an optional event to wait for
+ * }
+ */
+const attributesTests = [{
+ desc: 'Initiall accessible attributes',
+ expected: defaultAttributes,
+ unexpected: {
+ 'line-number': '1',
+ 'explicit-name': 'true',
+ 'container-live': 'polite',
+ 'live': 'polite'
+ }
+}, {
+ desc: '@line-number attribute is present when textbox is focused',
+ action: function*(browser) {
+ yield invokeFocus(browser, 'textbox');
+ },
+ waitFor: EVENT_FOCUS,
+ expected: Object.assign({}, defaultAttributes, { 'line-number': '1' }),
+ unexpected: {
+ 'explicit-name': 'true',
+ 'container-live': 'polite',
+ 'live': 'polite'
+ }
+}, {
+ desc: '@aria-live sets container-live and live attributes',
+ attrs: [{
+ attr: 'aria-live',
+ value: 'polite'
+ }],
+ expected: Object.assign({}, defaultAttributes, {
+ 'line-number': '1',
+ 'container-live': 'polite',
+ 'live': 'polite'
+ }),
+ unexpected: {
+ 'explicit-name': 'true'
+ }
+}, {
+ desc: '@title attribute sets explicit-name attribute to true',
+ attrs: [{
+ attr: 'title',
+ value: 'textbox'
+ }],
+ expected: Object.assign({}, defaultAttributes, {
+ 'line-number': '1',
+ 'explicit-name': 'true',
+ 'container-live': 'polite',
+ 'live': 'polite'
+ }),
+ unexpected: {}
+}];
+
+/**
+ * Test caching of accessible object attributes
+ */
+addAccessibleTask(`
+ <input id="textbox" value="hello">`,
+ function* (browser, accDoc) {
+ let textbox = findAccessibleChildByID(accDoc, 'textbox');
+ for (let { desc, action, attrs, expected, waitFor, unexpected } of attributesTests) {
+ info(desc);
+ let onUpdate;
+
+ if (waitFor) {
+ onUpdate = waitForEvent(waitFor, 'textbox');
+ }
+
+ if (action) {
+ yield action(browser);
+ } else if (attrs) {
+ for (let { attr, value } of attrs) {
+ yield invokeSetAttribute(browser, 'textbox', attr, value);
+ }
+ }
+
+ yield onUpdate;
+ testAttrs(textbox, expected);
+ testAbsentAttrs(textbox, unexpected);
+ }
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_description.js b/accessible/tests/browser/e10s/browser_caching_description.js
new file mode 100644
index 000000000..18ee58bd0
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_description.js
@@ -0,0 +1,164 @@
+/* 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/. */
+
+'use strict';
+
+/* global EVENT_DESCRIPTION_CHANGE, EVENT_NAME_CHANGE, EVENT_REORDER */
+
+loadScripts({ name: 'name.js', dir: MOCHITESTS_DIR });
+
+/**
+ * Test data has the format of:
+ * {
+ * desc {String} description for better logging
+ * expected {String} expected description value for a given accessible
+ * attrs {?Array} an optional list of attributes to update
+ * waitFor {?Array} an optional list of accessible events to wait for when
+ * attributes are updated
+ * }
+ */
+const tests = [{
+ desc: 'No description when there are no @alt, @title and @aria-describedby',
+ expected: ''
+}, {
+ desc: 'Description from @aria-describedby attribute',
+ attrs: [{
+ attr: 'aria-describedby',
+ value: 'description'
+ }],
+ waitFor: [{ eventType: EVENT_DESCRIPTION_CHANGE, id: 'image' }],
+ expected: 'aria description'
+}, {
+ desc: 'No description from @aria-describedby since it is the same as the ' +
+ '@alt attribute which is used as the name',
+ attrs: [{
+ attr: 'alt',
+ value: 'aria description'
+ }],
+ waitFor: [{ eventType: EVENT_REORDER, id: 'body' }],
+ expected: ''
+}, {
+ desc: 'Description from @aria-describedby attribute when @alt and ' +
+ '@aria-describedby are not the same',
+ attrs: [{
+ attr: 'aria-describedby',
+ value: 'description2'
+ }],
+ waitFor: [{ eventType: EVENT_DESCRIPTION_CHANGE, id: 'image' }],
+ expected: 'another description'
+}, {
+ desc: 'Description from @aria-describedby attribute when @title (used for ' +
+ 'name) and @aria-describedby are not the same',
+ attrs: [{
+ attr: 'alt'
+ }, {
+ attr: 'title',
+ value: 'title'
+ }],
+ waitFor: [{ eventType: EVENT_REORDER, id: 'body' }],
+ expected: 'another description'
+}, {
+ desc: 'No description from @aria-describedby since it is the same as the ' +
+ '@title attribute which is used as the name',
+ attrs: [{
+ attr: 'title',
+ value: 'another description'
+ }],
+ waitFor: [{ eventType: EVENT_NAME_CHANGE, id: 'image' }],
+ expected: ''
+}, {
+ desc: 'No description with only @title attribute which is used as the name',
+ attrs: [{
+ attr: 'aria-describedby'
+ }],
+ waitFor: [{ eventType: EVENT_DESCRIPTION_CHANGE, id: 'image' }],
+ expected: ''
+}, {
+ desc: 'Description from @title attribute when @alt and @atitle are not the ' +
+ 'same',
+ attrs: [{
+ attr: 'alt',
+ value: 'aria description'
+ }],
+ waitFor: [{ eventType: EVENT_REORDER, id: 'body' }],
+ expected: 'another description'
+}, {
+ desc: 'No description from @title since it is the same as the @alt ' +
+ 'attribute which is used as the name',
+ attrs: [{
+ attr: 'alt',
+ value: 'another description'
+ }],
+ waitFor: [{ eventType: EVENT_NAME_CHANGE, id: 'image' }],
+ expected: ''
+}, {
+ desc: 'No description from @aria-describedby since it is the same as the ' +
+ '@alt (used for name) and @title attributes',
+ attrs: [{
+ attr: 'aria-describedby',
+ value: 'description2'
+ }],
+ waitFor: [{ eventType: EVENT_DESCRIPTION_CHANGE, id: 'image' }],
+ expected: ''
+}, {
+ desc: 'Description from @aria-describedby attribute when it is different ' +
+ 'from @alt (used for name) and @title attributes',
+ attrs: [{
+ attr: 'aria-describedby',
+ value: 'description'
+ }],
+ waitFor: [{ eventType: EVENT_DESCRIPTION_CHANGE, id: 'image' }],
+ expected: 'aria description'
+}, {
+ desc: 'No description from @aria-describedby since it is the same as the ' +
+ '@alt attribute (used for name) but different from title',
+ attrs: [{
+ attr: 'alt',
+ value: 'aria description'
+ }],
+ waitFor: [{ eventType: EVENT_NAME_CHANGE, id: 'image' }],
+ expected: ''
+}, {
+ desc: 'Description from @aria-describedby attribute when @alt (used for ' +
+ 'name) and @aria-describedby are not the same but @title and ' +
+ 'aria-describedby are',
+ attrs: [{
+ attr: 'aria-describedby',
+ value: 'description2'
+ }],
+ waitFor: [{ eventType: EVENT_DESCRIPTION_CHANGE, id: 'image' }],
+ expected: 'another description'
+}];
+
+/**
+ * Test caching of accessible object description
+ */
+addAccessibleTask(`
+ <p id="description">aria description</p>
+ <p id="description2">another description</p>
+ <img id="image" />`,
+ function*(browser, accDoc) {
+ let imgAcc = findAccessibleChildByID(accDoc, 'image');
+
+ for (let { desc, waitFor, attrs, expected } of tests) {
+ info(desc);
+ let onUpdate;
+ if (waitFor) {
+ onUpdate = waitForMultipleEvents(waitFor);
+ }
+ if (attrs) {
+ for (let { attr, value } of attrs) {
+ yield invokeSetAttribute(browser, 'image', attr, value);
+ }
+ }
+ yield onUpdate;
+ // When attribute change (alt) triggers reorder event, accessible will
+ // become defunct.
+ if (isDefunct(imgAcc)) {
+ imgAcc = findAccessibleChildByID(accDoc, 'image');
+ }
+ testDescr(imgAcc, expected);
+ }
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_name.js b/accessible/tests/browser/e10s/browser_caching_name.js
new file mode 100644
index 000000000..08e635014
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_name.js
@@ -0,0 +1,434 @@
+/* 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/. */
+
+'use strict';
+
+/* global EVENT_REORDER, EVENT_TEXT_INSERTED */
+
+loadScripts({ name: 'name.js', dir: MOCHITESTS_DIR });
+
+/**
+ * Rules for name tests that are inspired by
+ * accessible/tests/mochitest/name/markuprules.xul
+ *
+ * Each element in the list of rules represents a name calculation rule for a
+ * particular test case.
+ *
+ * The rules have the following format:
+ * { attr } - calculated from attribute
+ * { elm } - calculated from another element
+ * { fromsubtree } - calculated from element's subtree
+ *
+ *
+ * Options include:
+ * * recreated - subrtee is recreated and the test should only continue
+ * after a reorder event
+ * * textchanged - text is inserted into a subtree and the test should only
+ * continue after a text inserted event
+ */
+const ARIARule = [{ attr: 'aria-labelledby' }, { attr: 'aria-label' }];
+const HTMLControlHeadRule = [...ARIARule, { elm: 'label', isSibling: true }];
+const rules = {
+ CSSContent: [{ elm: 'style', isSibling: true }, { fromsubtree: true }],
+ HTMLARIAGridCell: [...ARIARule, { fromsubtree: true }, { attr: 'title' }],
+ HTMLControl: [...HTMLControlHeadRule, { fromsubtree: true },
+ { attr: 'title' }],
+ HTMLElm: [...ARIARule, { attr: 'title' }],
+ HTMLImg: [...ARIARule, { attr: 'alt', recreated: true }, { attr: 'title' }],
+ HTMLImgEmptyAlt: [...ARIARule, { attr: 'title' }, { attr: 'alt' }],
+ HTMLInputButton: [...HTMLControlHeadRule, { attr: 'value' },
+ { attr: 'title' }],
+ HTMLInputImage: [...HTMLControlHeadRule, { attr: 'alt', recreated: true },
+ { attr: 'value', recreated: true }, { attr: 'title' }],
+ HTMLInputImageNoValidSrc: [...HTMLControlHeadRule,
+ { attr: 'alt', recreated: true }, { attr: 'value', recreated: true }],
+ HTMLInputReset: [...HTMLControlHeadRule,
+ { attr: 'value', textchanged: true }],
+ HTMLInputSubmit: [...HTMLControlHeadRule,
+ { attr: 'value', textchanged: true }],
+ HTMLLink: [...ARIARule, { fromsubtree: true }, { attr: 'title' }],
+ HTMLLinkImage: [...ARIARule, { elm: 'img' }, { attr: 'title' }],
+ HTMLOption: [...ARIARule, { attr: 'label' }, { fromsubtree: true },
+ { attr: 'title' }],
+ HTMLTable: [...ARIARule, { elm: 'caption' }, { attr: 'summary' },
+ { attr: 'title' }]
+};
+
+const markupTests = [{
+ id: 'btn',
+ ruleset: 'HTMLControl',
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <label for="btn">test4</label>
+ <button id="btn"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ title="test5">press me</button>`,
+ expected: ['test2 test3', 'test1', 'test4', 'press me', 'test5']
+}, {
+ id: 'btn',
+ ruleset: 'HTMLInputButton',
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <label for="btn">test4</label>
+ <input id="btn"
+ type="button"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ value="name from value"
+ alt="no name from al"
+ src="no name from src"
+ data="no name from data"
+ title="name from title"/>`,
+ expected: ['test2 test3', 'test1', 'test4', 'name from value',
+ 'name from title']
+}, {
+ id: 'btn-submit',
+ ruleset: 'HTMLInputSubmit',
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <label for="btn-submit">test4</label>
+ <input id="btn-submit"
+ type="submit"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ value="name from value"
+ alt="no name from atl"
+ src="no name from src"
+ data="no name from data"
+ title="no name from title"/>`,
+ expected: ['test2 test3', 'test1', 'test4', 'name from value']
+}, {
+ id: 'btn-reset',
+ ruleset: 'HTMLInputReset',
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <label for="btn-reset">test4</label>
+ <input id="btn-reset"
+ type="reset"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ value="name from value"
+ alt="no name from alt"
+ src="no name from src"
+ data="no name from data"
+ title="no name from title"/>`,
+ expected: ['test2 test3', 'test1', 'test4', 'name from value']
+}, {
+ id: 'btn-image',
+ ruleset: 'HTMLInputImage',
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <label for="btn-image">test4</label>
+ <input id="btn-image"
+ type="image"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ alt="name from alt"
+ value="name from value"
+ src="http://example.com/a11y/accessible/tests/mochitest/moz.png"
+ data="no name from data"
+ title="name from title"/>`,
+ expected: ['test2 test3', 'test1', 'test4', 'name from alt',
+ 'name from value', 'name from title']
+}, {
+ id: 'btn-image',
+ ruleset: 'HTMLInputImageNoValidSrc',
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <label for="btn-image">test4</label>
+ <input id="btn-image"
+ type="image"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ alt="name from alt"
+ value="name from value"
+ data="no name from data"
+ title="no name from title"/>`,
+ expected: ['test2 test3', 'test1', 'test4', 'name from alt',
+ 'name from value']
+}, {
+ id: 'opt',
+ ruleset: 'HTMLOption',
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <select>
+ <option id="opt"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ label="test4"
+ title="test5">option1</option>
+ <option>option2</option>
+ </select>`,
+ expected: ['test2 test3', 'test1', 'test4', 'option1', 'test5']
+}, {
+ id: 'img',
+ ruleset: 'HTMLImg',
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <img id="img"
+ aria-label="Logo of Mozilla"
+ aria-labelledby="l1 l2"
+ alt="Mozilla logo"
+ title="This is a logo"
+ src="http://example.com/a11y/accessible/tests/mochitest/moz.png"/>`,
+ expected: ['test2 test3', 'Logo of Mozilla', 'Mozilla logo', 'This is a logo']
+}, {
+ id: 'imgemptyalt',
+ ruleset: 'HTMLImgEmptyAlt',
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <img id="imgemptyalt"
+ aria-label="Logo of Mozilla"
+ aria-labelledby="l1 l2"
+ title="This is a logo"
+ alt=""
+ src="http://example.com/a11y/accessible/tests/mochitest/moz.png"/>`,
+ expected: ['test2 test3', 'Logo of Mozilla', 'This is a logo', '']
+}, {
+ id: 'tc',
+ ruleset: 'HTMLElm',
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <label for="tc">test4</label>
+ <table>
+ <tr>
+ <td id="tc"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ title="test5">
+ <p>This is a paragraph</p>
+ <a href="#">This is a link</a>
+ <ul>
+ <li>This is a list</li>
+ </ul>
+ </td>
+ </tr>
+ </table>`,
+ expected: ['test2 test3', 'test1', 'test5']
+}, {
+ id: 'gc',
+ ruleset: 'HTMLARIAGridCell',
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <label for="gc">test4</label>
+ <table>
+ <tr>
+ <td id="gc"
+ role="gridcell"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ title="This is a paragraph This is a link This is a list">
+ <p>This is a paragraph</p>
+ <a href="#">This is a link</a>
+ <ul>
+ <li>Listitem1</li>
+ <li>Listitem2</li>
+ </ul>
+ </td>
+ </tr>
+ </table>`,
+ expected: ['test2 test3', 'test1', 'This is a paragraph',
+ 'This is a paragraph This is a link This is a list']
+}, {
+ id: 't',
+ ruleset: 'HTMLTable',
+ markup: `
+ <span id="l1">lby_tst6_1</span>
+ <span id="l2">lby_tst6_2</span>
+ <label for="t">label_tst6</label>
+ <table id="t"
+ aria-label="arialabel_tst6"
+ aria-labelledby="l1 l2"
+ summary="summary_tst6"
+ title="title_tst6">
+ <caption>caption_tst6</caption>
+ <tr>
+ <td>cell1</td>
+ <td>cell2</td>
+ </tr>
+ </table>`,
+ expected: ['lby_tst6_1 lby_tst6_2', 'arialabel_tst6', 'caption_tst6',
+ 'summary_tst6', 'title_tst6']
+}, {
+ id: 'btn',
+ ruleset: 'CSSContent',
+ markup: `
+ <style>
+ button::before {
+ content: "do not ";
+ }
+ </style>
+ <button id="btn">press me</button>`,
+ expected: ['do not press me', 'press me']
+}, {
+ // TODO: uncomment when Bug-1256382 is resoved.
+ // id: 'li',
+ // ruleset: 'CSSContent',
+ // markup: `
+ // <style>
+ // ul {
+ // list-style-type: decimal;
+ // }
+ // </style>
+ // <ul id="ul">
+ // <li id="li">Listitem</li>
+ // </ul>`,
+ // expected: ['1. Listitem', `${String.fromCharCode(0x2022)} Listitem`]
+// }, {
+ id: 'a',
+ ruleset: 'HTMLLink',
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <a id="a"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ title="test4">test5</a>`,
+ expected: ['test2 test3', 'test1', 'test5', 'test4']
+}, {
+ id: 'a-img',
+ ruleset: 'HTMLLinkImage',
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <a id="a-img"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ title="test4"><img alt="test5"/></a>`,
+ expected: ['test2 test3', 'test1', 'test5', 'test4']
+}];
+
+/**
+ * Wait for an accessible event to happen and, in case given accessible is
+ * defunct, update it to one that is attached to the accessible event.
+ * @param {Promise} onEvent accessible event promise
+ * @param {Object} target { acc, parent, id } structure that contains an
+ * accessible, its parent and its content element
+ * id.
+ */
+function* updateAccessibleIfNeeded(onEvent, target) {
+ let event = yield onEvent;
+ if (isDefunct(target.acc)) {
+ target.acc = findAccessibleChildByID(event.accessible, target.id);
+ }
+}
+
+/**
+ * Test accessible name that is calculated from an attribute, remove the
+ * attribute before proceeding to the next name test. If attribute removal
+ * results in a reorder or text inserted event - wait for it. If accessible
+ * becomes defunct, update its reference using the one that is attached to one
+ * of the above events.
+ * @param {Object} browser current "tabbrowser" element
+ * @param {Object} target { acc, parent, id } structure that contains an
+ * accessible, its parent and its content element
+ * id.
+ * @param {Object} rule current attr rule for name calculation
+ * @param {[type]} expected expected name value
+ */
+function* testAttrRule(browser, target, rule, expected) {
+ testName(target.acc, expected);
+ let onEvent;
+ if (rule.recreated) {
+ onEvent = waitForEvent(EVENT_REORDER, target.parent);
+ } else if (rule.textchanged) {
+ onEvent = waitForEvent(EVENT_TEXT_INSERTED, target.id);
+ }
+ yield invokeSetAttribute(browser, target.id, rule.attr);
+ if (onEvent) {
+ yield updateAccessibleIfNeeded(onEvent, target);
+ }
+}
+
+/**
+ * Test accessible name that is calculated from an element name, remove the
+ * element before proceeding to the next name test. If element removal results
+ * in a reorder event - wait for it. If accessible becomes defunct, update its
+ * reference using the one that is attached to a possible reorder event.
+ * @param {Object} browser current "tabbrowser" element
+ * @param {Object} target { acc, parent, id } structure that contains an
+ * accessible, its parent and its content element
+ * id.
+ * @param {Object} rule current elm rule for name calculation
+ * @param {[type]} expected expected name value
+ */
+function* testElmRule(browser, target, rule, expected) {
+ testName(target.acc, expected);
+ let onEvent = waitForEvent(EVENT_REORDER, rule.isSibling ?
+ target.parent : target.id);
+ yield ContentTask.spawn(browser, rule.elm, elm =>
+ content.document.querySelector(`${elm}`).remove());
+ yield updateAccessibleIfNeeded(onEvent, target);
+}
+
+/**
+ * Test accessible name that is calculated from its subtree, remove the subtree
+ * and wait for a reorder event before proceeding to the next name test. If
+ * accessible becomes defunct, update its reference using the one that is
+ * attached to a reorder event.
+ * @param {Object} browser current "tabbrowser" element
+ * @param {Object} target { acc, parent, id } structure that contains an
+ * accessible, its parent and its content element
+ * id.
+ * @param {Object} rule current subtree rule for name calculation
+ * @param {[type]} expected expected name value
+ */
+function* testSubtreeRule(browser, target, rule, expected) {
+ testName(target.acc, expected);
+ let onEvent = waitForEvent(EVENT_REORDER, target.id);
+ yield ContentTask.spawn(browser, target.id, id => {
+ let elm = content.document.getElementById(id);
+ while (elm.firstChild) {
+ elm.removeChild(elm.firstChild);
+ }
+ });
+ yield updateAccessibleIfNeeded(onEvent, target);
+}
+
+/**
+ * Iterate over a list of rules and test accessible names for each one of the
+ * rules.
+ * @param {Object} browser current "tabbrowser" element
+ * @param {Object} target { acc, parent, id } structure that contains an
+ * accessible, its parent and its content element
+ * id.
+ * @param {Array} ruleset A list of rules to test a target with
+ * @param {Array} expected A list of expected name value for each rule
+ */
+function* testNameRule(browser, target, ruleset, expected) {
+ for (let i = 0; i < ruleset.length; ++i) {
+ let rule = ruleset[i];
+ let testFn;
+ if (rule.attr) {
+ testFn = testAttrRule;
+ } else if (rule.elm) {
+ testFn = testElmRule;
+ } else if (rule.fromsubtree) {
+ testFn = testSubtreeRule;
+ }
+ yield testFn(browser, target, rule, expected[i]);
+ }
+}
+
+markupTests.forEach(({ id, ruleset, markup, expected }) =>
+ addAccessibleTask(markup, function*(browser, accDoc) {
+ // Find a target accessible from an accessible subtree.
+ let acc = findAccessibleChildByID(accDoc, id);
+ // Find target's parent accessible from an accessible subtree.
+ let parent = getAccessibleDOMNodeID(acc.parent);
+ let target = { id, parent, acc };
+ yield testNameRule(browser, target, rules[ruleset], expected);
+ }));
diff --git a/accessible/tests/browser/e10s/browser_caching_relations.js b/accessible/tests/browser/e10s/browser_caching_relations.js
new file mode 100644
index 000000000..772aee96a
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_relations.js
@@ -0,0 +1,86 @@
+/* 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/. */
+
+'use strict';
+
+/* global RELATION_LABELLED_BY, RELATION_LABEL_FOR, RELATION_DESCRIBED_BY,
+ RELATION_DESCRIPTION_FOR, RELATION_CONTROLLER_FOR,
+ RELATION_CONTROLLED_BY, RELATION_FLOWS_TO, RELATION_FLOWS_FROM */
+
+loadScripts({ name: 'relations.js', dir: MOCHITESTS_DIR });
+
+/**
+ * A test specification that has the following format:
+ * [
+ * attr relevant aria attribute
+ * hostRelation corresponding host relation type
+ * dependantRelation corresponding dependant relation type
+ * ]
+ */
+const attrRelationsSpec = [
+ ['aria-labelledby', RELATION_LABELLED_BY, RELATION_LABEL_FOR],
+ ['aria-describedby', RELATION_DESCRIBED_BY, RELATION_DESCRIPTION_FOR],
+ ['aria-controls', RELATION_CONTROLLER_FOR, RELATION_CONTROLLED_BY],
+ ['aria-flowto', RELATION_FLOWS_TO, RELATION_FLOWS_FROM]
+];
+
+function* testRelated(browser, accDoc, attr, hostRelation, dependantRelation) {
+ let host = findAccessibleChildByID(accDoc, 'host');
+ let dependant1 = findAccessibleChildByID(accDoc, 'dependant1');
+ let dependant2 = findAccessibleChildByID(accDoc, 'dependant2');
+
+ /**
+ * Test data has the format of:
+ * {
+ * desc {String} description for better logging
+ * attrs {?Array} an optional list of attributes to update
+ * expected {Array} expected relation values for dependant1, dependant2
+ * and host respectively.
+ * }
+ */
+ const tests = [{
+ desc: 'No attribute',
+ expected: [ null, null, null ]
+ }, {
+ desc: 'Set attribute',
+ attrs: [{ key: attr, value: 'dependant1' }],
+ expected: [ host, null, dependant1 ]
+ }, {
+ desc: 'Change attribute',
+ attrs: [{ key: attr, value: 'dependant2' }],
+ expected: [ null, host, dependant2 ]
+ }, {
+ desc: 'Remove attribute',
+ attrs: [{ key: attr }],
+ expected: [ null, null, null ]
+ }];
+
+ for (let { desc, attrs, expected } of tests) {
+ info(desc);
+
+ if (attrs) {
+ for (let { key, value } of attrs) {
+ yield invokeSetAttribute(browser, 'host', key, value);
+ }
+ }
+
+ testRelation(dependant1, dependantRelation, expected[0]);
+ testRelation(dependant2, dependantRelation, expected[1]);
+ testRelation(host, hostRelation, expected[2]);
+ }
+}
+
+/**
+ * Test caching of relations between accessible objects.
+ */
+addAccessibleTask(`
+ <div id="dependant1">label</div>
+ <div id="dependant2">label2</div>
+ <div role="checkbox" id="host"></div>`,
+ function* (browser, accDoc) {
+ for (let spec of attrRelationsSpec) {
+ yield testRelated(browser, accDoc, ...spec);
+ }
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_states.js b/accessible/tests/browser/e10s/browser_caching_states.js
new file mode 100644
index 000000000..69e4931ea
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_states.js
@@ -0,0 +1,120 @@
+/* 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/. */
+
+'use strict';
+
+/* global EVENT_STATE_CHANGE, STATE_CHECKED, STATE_BUSY, STATE_REQUIRED,
+ STATE_INVALID, EXT_STATE_ENABLED */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR },
+ { name: 'states.js', dir: MOCHITESTS_DIR });
+
+/**
+ * Test data has the format of:
+ * {
+ * desc {String} description for better logging
+ * expected {Array} expected states for a given accessible that have the
+ * following format:
+ * [
+ * expected state,
+ * expected extra state,
+ * absent state,
+ * absent extra state
+ * ]
+ * attrs {?Array} an optional list of attributes to update
+ * }
+ */
+
+// State caching tests for attribute changes
+const attributeTests = [{
+ desc: 'Checkbox with @checked attribute set to true should have checked ' +
+ 'state',
+ attrs: [{
+ attr: 'checked',
+ value: 'true'
+ }],
+ expected: [STATE_CHECKED, 0]
+}, {
+ desc: 'Checkbox with no @checked attribute should not have checked state',
+ attrs: [{
+ attr: 'checked'
+ }],
+ expected: [0, 0, STATE_CHECKED]
+}];
+
+// State caching tests for ARIA changes
+const ariaTests = [{
+ desc: 'File input has busy state when @aria-busy attribute is set to true',
+ attrs: [{
+ attr: 'aria-busy',
+ value: 'true'
+ }],
+ expected: [STATE_BUSY, 0, STATE_REQUIRED | STATE_INVALID]
+}, {
+ desc: 'File input has required state when @aria-required attribute is set ' +
+ 'to true',
+ attrs: [{
+ attr: 'aria-required',
+ value: 'true'
+ }],
+ expected: [STATE_REQUIRED, 0, STATE_INVALID]
+}, {
+ desc: 'File input has invalid state when @aria-invalid attribute is set to ' +
+ 'true',
+ attrs: [{
+ attr: 'aria-invalid',
+ value: 'true'
+ }],
+ expected: [STATE_INVALID, 0]
+}];
+
+// Extra state caching tests
+const extraStateTests = [{
+ desc: 'Input has no extra enabled state when aria and native disabled ' +
+ 'attributes are set at once',
+ attrs: [{
+ attr: 'aria-disabled',
+ value: 'true'
+ }, {
+ attr: 'disabled',
+ value: 'true'
+ }],
+ expected: [0, 0, 0, EXT_STATE_ENABLED]
+}, {
+ desc: 'Input has an extra enabled state when aria and native disabled ' +
+ 'attributes are unset at once',
+ attrs: [{
+ attr: 'aria-disabled'
+ }, {
+ attr: 'disabled'
+ }],
+ expected: [0, EXT_STATE_ENABLED]
+}];
+
+function* runStateTests(browser, accDoc, id, tests) {
+ let acc = findAccessibleChildByID(accDoc, id);
+ for (let { desc, attrs, expected } of tests) {
+ info(desc);
+ let onUpdate = waitForEvent(EVENT_STATE_CHANGE, id);
+ for (let { attr, value } of attrs) {
+ yield invokeSetAttribute(browser, id, attr, value);
+ }
+ yield onUpdate;
+ testStates(acc, ...expected);
+ }
+}
+
+/**
+ * Test caching of accessible object states
+ */
+addAccessibleTask(`
+ <input id="checkbox" type="checkbox">
+ <input id="file" type="file">
+ <input id="text">`,
+ function* (browser, accDoc) {
+ yield runStateTests(browser, accDoc, 'checkbox', attributeTests);
+ yield runStateTests(browser, accDoc, 'file', ariaTests);
+ yield runStateTests(browser, accDoc, 'text', extraStateTests);
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_value.js b/accessible/tests/browser/e10s/browser_caching_value.js
new file mode 100644
index 000000000..2669cbfab
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_value.js
@@ -0,0 +1,155 @@
+/* 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/. */
+
+'use strict';
+
+/* global nsIAccessibleValue, EVENT_VALUE_CHANGE, EVENT_TEXT_VALUE_CHANGE */
+
+loadScripts({ name: 'value.js', dir: MOCHITESTS_DIR });
+
+/**
+ * Test data has the format of:
+ * {
+ * desc {String} description for better logging
+ * id {String} given accessible DOMNode ID
+ * expected {String} expected value for a given accessible
+ * action {?Function*} an optional action that yields a value change
+ * attrs {?Array} an optional list of attributes to update
+ * waitFor {?Number} an optional value change event to wait for
+ * }
+ */
+const valueTests = [{
+ desc: 'Initially value is set to 1st element of select',
+ id: 'select',
+ expected: '1st'
+}, {
+ desc: 'Value should update to 3rd when 3 is pressed',
+ id: 'select',
+ action: function*(browser) {
+ yield invokeFocus(browser, 'select');
+ yield BrowserTestUtils.synthesizeKey('3', {}, browser);
+ },
+ waitFor: EVENT_TEXT_VALUE_CHANGE,
+ expected: '3rd'
+}, {
+ desc: 'Initially value is set to @aria-valuenow for slider',
+ id: 'slider',
+ expected: ['5', 5, 0, 7, 0]
+}, {
+ desc: 'Value should change when @aria-valuenow is updated',
+ id: 'slider',
+ attrs: [{
+ attr: 'aria-valuenow',
+ value: '6'
+ }],
+ waitFor: EVENT_VALUE_CHANGE,
+ expected: ['6', 6, 0, 7, 0]
+}, {
+ desc: 'Value should change when @aria-valuetext is set',
+ id: 'slider',
+ attrs: [{
+ attr: 'aria-valuetext',
+ value: 'plain'
+ }],
+ waitFor: EVENT_TEXT_VALUE_CHANGE,
+ expected: ['plain', 6, 0, 7, 0]
+}, {
+ desc: 'Value should change when @aria-valuetext is updated',
+ id: 'slider',
+ attrs: [{
+ attr: 'aria-valuetext',
+ value: 'hey!'
+ }],
+ waitFor: EVENT_TEXT_VALUE_CHANGE,
+ expected: ['hey!', 6, 0, 7, 0]
+}, {
+ desc: 'Value should change to @aria-valuetext when @aria-valuenow is removed',
+ id: 'slider',
+ attrs: [{
+ attr: 'aria-valuenow'
+ }],
+ expected: ['hey!', 0, 0, 7, 0]
+}, {
+ desc: 'Initially value is not set for combobox',
+ id: 'combobox',
+ expected: ''
+}, {
+ desc: 'Value should change when @value attribute is updated',
+ id: 'combobox',
+ attrs: [{
+ attr: 'value',
+ value: 'hello'
+ }],
+ waitFor: EVENT_TEXT_VALUE_CHANGE,
+ expected: 'hello'
+}, {
+ desc: 'Initially value corresponds to @value attribute for progress',
+ id: 'progress',
+ expected: '22%'
+}, {
+ desc: 'Value should change when @value attribute is updated',
+ id: 'progress',
+ attrs: [{
+ attr: 'value',
+ value: '50'
+ }],
+ waitFor: EVENT_VALUE_CHANGE,
+ expected: '50%'
+}, {
+ desc: 'Initially value corresponds to @value attribute for range',
+ id: 'range',
+ expected: '6'
+}, {
+ desc: 'Value should change when slider is moved',
+ id: 'range',
+ action: function*(browser) {
+ yield invokeFocus(browser, 'range');
+ yield BrowserTestUtils.synthesizeKey('VK_LEFT', {}, browser);
+ },
+ waitFor: EVENT_VALUE_CHANGE,
+ expected: '5'
+}];
+
+/**
+ * Test caching of accessible object values
+ */
+addAccessibleTask(`
+ <div id="slider" role="slider" aria-valuenow="5"
+ aria-valuemin="0" aria-valuemax="7">slider</div>
+ <select id="select">
+ <option>1st</option>
+ <option>2nd</option>
+ <option>3rd</option>
+ </select>
+ <input id="combobox" role="combobox" aria-autocomplete="inline">
+ <progress id="progress" value="22" max="100"></progress>
+ <input type="range" id="range" min="0" max="10" value="6">`,
+ function* (browser, accDoc) {
+ for (let { desc, id, action, attrs, expected, waitFor } of valueTests) {
+ info(desc);
+ let acc = findAccessibleChildByID(accDoc, id);
+ let onUpdate;
+
+ if (waitFor) {
+ onUpdate = waitForEvent(waitFor, id);
+ }
+
+ if (action) {
+ yield action(browser);
+ } else if (attrs) {
+ for (let { attr, value } of attrs) {
+ yield invokeSetAttribute(browser, id, attr, value);
+ }
+ }
+
+ yield onUpdate;
+ if (Array.isArray(expected)) {
+ acc.QueryInterface(nsIAccessibleValue);
+ testValue(acc, ...expected);
+ } else {
+ is(acc.value, expected, `Correct value for ${prettyName(acc)}`);
+ }
+ }
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_events_caretmove.js b/accessible/tests/browser/e10s/browser_events_caretmove.js
new file mode 100644
index 000000000..506945f30
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_events_caretmove.js
@@ -0,0 +1,21 @@
+/* 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/. */
+
+/* global EVENT_TEXT_CARET_MOVED, nsIAccessibleCaretMoveEvent */
+
+'use strict';
+
+/**
+ * Test caret move event and its interface:
+ * - caretOffset
+ */
+addAccessibleTask('<input id="textbox" value="hello"/>', function*(browser) {
+ let onCaretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, 'textbox');
+ yield invokeFocus(browser, 'textbox');
+ let event = yield onCaretMoved;
+
+ let caretMovedEvent = event.QueryInterface(nsIAccessibleCaretMoveEvent);
+ is(caretMovedEvent.caretOffset, 5,
+ 'Correct caret offset.');
+});
diff --git a/accessible/tests/browser/e10s/browser_events_hide.js b/accessible/tests/browser/e10s/browser_events_hide.js
new file mode 100644
index 000000000..bb9ee3961
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_events_hide.js
@@ -0,0 +1,35 @@
+/* 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/. */
+
+/* global EVENT_HIDE */
+
+'use strict';
+
+/**
+ * Test hide event and its interface:
+ * - targetParent
+ * - targetNextSibling
+ * - targetPrevSibling
+ */
+addAccessibleTask(`
+ <div id="parent">
+ <div id="previous"></div>
+ <div id="to-hide"></div>
+ <div id="next"></div>
+ </div>`,
+ function*(browser, accDoc) {
+ let acc = findAccessibleChildByID(accDoc, 'to-hide');
+ let onHide = waitForEvent(EVENT_HIDE, acc);
+ yield invokeSetStyle(browser, 'to-hide', 'visibility', 'hidden');
+ let event = yield onHide;
+ let hideEvent = event.QueryInterface(Ci.nsIAccessibleHideEvent);
+
+ is(getAccessibleDOMNodeID(hideEvent.targetParent), 'parent',
+ 'Correct target parent.');
+ is(getAccessibleDOMNodeID(hideEvent.targetNextSibling), 'next',
+ 'Correct target next sibling.');
+ is(getAccessibleDOMNodeID(hideEvent.targetPrevSibling), 'previous',
+ 'Correct target previous sibling.');
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_events_show.js b/accessible/tests/browser/e10s/browser_events_show.js
new file mode 100644
index 000000000..5003c14f7
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_events_show.js
@@ -0,0 +1,17 @@
+/* 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/. */
+
+/* global EVENT_SHOW */
+
+'use strict';
+
+/**
+ * Test show event
+ */
+addAccessibleTask('<div id="div" style="visibility: hidden;"></div>',
+ function*(browser) {
+ let onShow = waitForEvent(EVENT_SHOW, 'div');
+ yield invokeSetStyle(browser, 'div', 'visibility', 'visible');
+ yield onShow;
+ });
diff --git a/accessible/tests/browser/e10s/browser_events_statechange.js b/accessible/tests/browser/e10s/browser_events_statechange.js
new file mode 100644
index 000000000..7f353efa7
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_events_statechange.js
@@ -0,0 +1,62 @@
+/* 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/. */
+
+/* global STATE_CHECKED, EXT_STATE_EDITABLE, nsIAccessibleStateChangeEvent,
+ EVENT_STATE_CHANGE */
+
+'use strict';
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR },
+ { name: 'states.js', dir: MOCHITESTS_DIR });
+
+function checkStateChangeEvent(event, state, isExtraState, isEnabled) {
+ let scEvent = event.QueryInterface(nsIAccessibleStateChangeEvent);
+ is(scEvent.state, state, 'Correct state of the statechange event.');
+ is(scEvent.isExtraState, isExtraState,
+ 'Correct extra state bit of the statechange event.');
+ is(scEvent.isEnabled, isEnabled, 'Correct state of statechange event state');
+}
+
+// Insert mock source into the iframe to be able to verify the right document
+// body id.
+let iframeSrc = `data:text/html,
+ <html>
+ <head>
+ <meta charset='utf-8'/>
+ <title>Inner Iframe</title>
+ </head>
+ <body id='iframe'></body>
+ </html>`;
+
+/**
+ * Test state change event and its interface:
+ * - state
+ * - isExtraState
+ * - isEnabled
+ */
+addAccessibleTask(`
+ <iframe id="iframe" src="${iframeSrc}"></iframe>
+ <input id="checkbox" type="checkbox" />`, function*(browser) {
+ // Test state change
+ let onStateChange = waitForEvent(EVENT_STATE_CHANGE, 'checkbox');
+ // Set checked for a checkbox.
+ yield ContentTask.spawn(browser, {}, () => {
+ content.document.getElementById('checkbox').checked = true;
+ });
+ let event = yield onStateChange;
+
+ checkStateChangeEvent(event, STATE_CHECKED, false, true);
+ testStates(event.accessible, STATE_CHECKED, 0);
+
+ // Test extra state
+ onStateChange = waitForEvent(EVENT_STATE_CHANGE, 'iframe');
+ // Set design mode on.
+ yield ContentTask.spawn(browser, {}, () => {
+ content.document.getElementById('iframe').contentDocument.designMode = 'on';
+ });
+ event = yield onStateChange;
+
+ checkStateChangeEvent(event, EXT_STATE_EDITABLE, true, true);
+ testStates(event.accessible, 0, EXT_STATE_EDITABLE);
+});
diff --git a/accessible/tests/browser/e10s/browser_events_textchange.js b/accessible/tests/browser/e10s/browser_events_textchange.js
new file mode 100644
index 000000000..a1aed52d1
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_events_textchange.js
@@ -0,0 +1,74 @@
+/* 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/. */
+
+/* global EVENT_TEXT_INSERTED, EVENT_TEXT_REMOVED,
+ nsIAccessibleTextChangeEvent */
+
+'use strict';
+
+function checkTextChangeEvent(event, id, text, start, end, isInserted, isFromUserInput) {
+ let tcEvent = event.QueryInterface(nsIAccessibleTextChangeEvent);
+ is(tcEvent.start, start, `Correct start offset for ${prettyName(id)}`);
+ is(tcEvent.length, end - start, `Correct length for ${prettyName(id)}`);
+ is(tcEvent.isInserted, isInserted,
+ `Correct isInserted flag for ${prettyName(id)}`);
+ is(tcEvent.modifiedText, text, `Correct text for ${prettyName(id)}`);
+ is(tcEvent.isFromUserInput, isFromUserInput,
+ `Correct value of isFromUserInput for ${prettyName(id)}`);
+}
+
+function* changeText(browser, id, value, events) {
+ let onEvents = waitForMultipleEvents(events.map(({ isInserted }) => {
+ let eventType = isInserted ? EVENT_TEXT_INSERTED : EVENT_TEXT_REMOVED;
+ return { id, eventType };
+ }));
+ // Change text in the subtree.
+ yield ContentTask.spawn(browser, [id, value], ([contentId, contentValue]) => {
+ content.document.getElementById(contentId).firstChild.textContent =
+ contentValue;
+ });
+ let resolvedEvents = yield onEvents;
+
+ events.forEach(({ isInserted, str, offset }, idx) =>
+ checkTextChangeEvent(resolvedEvents[idx],
+ id, str, offset, offset + str.length, isInserted, false));
+}
+
+function* removeTextFromInput(browser, id, value, start, end) {
+ let onTextRemoved = waitForEvent(EVENT_TEXT_REMOVED, id);
+ // Select text and delete it.
+ yield ContentTask.spawn(browser, [id, start, end], ([contentId, contentStart, contentEnd]) => {
+ let el = content.document.getElementById(contentId);
+ el.focus();
+ el.setSelectionRange(contentStart, contentEnd);
+ });
+ yield BrowserTestUtils.sendChar('VK_DELETE', browser);
+
+ let event = yield onTextRemoved;
+ checkTextChangeEvent(event, id, value, start, end, false, true);
+}
+
+/**
+ * Test text change event and its interface:
+ * - start
+ * - length
+ * - isInserted
+ * - modifiedText
+ * - isFromUserInput
+ */
+addAccessibleTask(`
+ <p id="p">abc</p>
+ <input id="input" value="input" />`, function*(browser) {
+ let events = [
+ { isInserted: false, str: 'abc', offset: 0 },
+ { isInserted: true, str: 'def', offset: 0 }
+ ];
+ yield changeText(browser, 'p', 'def', events);
+
+ events = [{ isInserted: true, str: 'DEF', offset: 2 }];
+ yield changeText(browser, 'p', 'deDEFf', events);
+
+ // Test isFromUserInput property.
+ yield removeTextFromInput(browser, 'input', 'n', 1, 2);
+});
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_ariadialog.js b/accessible/tests/browser/e10s/browser_treeupdate_ariadialog.js
new file mode 100644
index 000000000..a9544bc5c
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_ariadialog.js
@@ -0,0 +1,43 @@
+/* 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/. */
+
+'use strict';
+
+/* global EVENT_SHOW, ROLE_DIALOG, ROLE_PUSHBUTTON, ROLE_TEXT_LEAF, ROLE_ENTRY,
+ ROLE_DOCUMENT */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+// Test ARIA Dialog
+addAccessibleTask('doc_treeupdate_ariadialog.html', function*(browser, accDoc) {
+ testAccessibleTree(accDoc, {
+ role: ROLE_DOCUMENT,
+ children: [ ]
+ });
+
+ // Make dialog visible and update its inner content.
+ let onShow = waitForEvent(EVENT_SHOW, 'dialog');
+ yield ContentTask.spawn(browser, {}, () => {
+ content.document.getElementById('dialog').style.display = 'block';
+ });
+ yield onShow;
+
+ testAccessibleTree(accDoc, {
+ role: ROLE_DOCUMENT,
+ children: [
+ {
+ role: ROLE_DIALOG,
+ children: [
+ {
+ role: ROLE_PUSHBUTTON,
+ children: [ { role: ROLE_TEXT_LEAF } ]
+ },
+ {
+ role: ROLE_ENTRY
+ }
+ ]
+ }
+ ]
+ });
+});
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js b/accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js
new file mode 100644
index 000000000..998da32a1
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js
@@ -0,0 +1,318 @@
+/* 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/. */
+
+'use strict';
+
+/* global EVENT_REORDER */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+function* testContainer1(browser, accDoc) {
+ const id = 't1_container';
+ const docID = getAccessibleDOMNodeID(accDoc);
+ const acc = findAccessibleChildByID(accDoc, id);
+
+ /* ================= Initial tree test ==================================== */
+ // children are swapped by ARIA owns
+ let tree = {
+ SECTION: [
+ { CHECKBUTTON: [
+ { SECTION: [] }
+ ] },
+ { PUSHBUTTON: [ ] }
+ ]
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================ Change ARIA owns ====================================== */
+ let onReorder = waitForEvent(EVENT_REORDER, id);
+ yield invokeSetAttribute(browser, id, 'aria-owns', 't1_button t1_subdiv');
+ yield onReorder;
+
+ // children are swapped again, button and subdiv are appended to
+ // the children.
+ tree = {
+ SECTION: [
+ { CHECKBUTTON: [ ] }, // checkbox, native order
+ { PUSHBUTTON: [ ] }, // button, rearranged by ARIA own
+ { SECTION: [ ] } // subdiv from the subtree, ARIA owned
+ ]
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================ Remove ARIA owns ====================================== */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ yield invokeSetAttribute(browser, id, 'aria-owns');
+ yield onReorder;
+
+ // children follow the DOM order
+ tree = {
+ SECTION: [
+ { PUSHBUTTON: [ ] },
+ { CHECKBUTTON: [
+ { SECTION: [] }
+ ] }
+ ]
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================ Set ARIA owns ========================================= */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ yield invokeSetAttribute(browser, id, 'aria-owns', 't1_button t1_subdiv');
+ yield onReorder;
+
+ // children are swapped again, button and subdiv are appended to
+ // the children.
+ tree = {
+ SECTION: [
+ { CHECKBUTTON: [ ] }, // checkbox
+ { PUSHBUTTON: [ ] }, // button, rearranged by ARIA own
+ { SECTION: [ ] } // subdiv from the subtree, ARIA owned
+ ]
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================ Add ID to ARIA owns =================================== */
+ onReorder = waitForEvent(EVENT_REORDER, docID);
+ yield invokeSetAttribute(browser, id, 'aria-owns',
+ 't1_button t1_subdiv t1_group');
+ yield onReorder;
+
+ // children are swapped again, button and subdiv are appended to
+ // the children.
+ tree = {
+ SECTION: [
+ { CHECKBUTTON: [ ] }, // t1_checkbox
+ { PUSHBUTTON: [ ] }, // button, t1_button
+ { SECTION: [ ] }, // subdiv from the subtree, t1_subdiv
+ { GROUPING: [ ] } // group from outside, t1_group
+ ]
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================ Append element ======================================== */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ yield ContentTask.spawn(browser, id, contentId => {
+ let div = content.document.createElement('div');
+ div.setAttribute('id', 't1_child3');
+ div.setAttribute('role', 'radio');
+ content.document.getElementById(contentId).appendChild(div);
+ });
+ yield onReorder;
+
+ // children are invalidated, they includes aria-owns swapped kids and
+ // newly inserted child.
+ tree = {
+ SECTION: [
+ { CHECKBUTTON: [ ] }, // existing explicit, t1_checkbox
+ { RADIOBUTTON: [ ] }, // new explicit, t1_child3
+ { PUSHBUTTON: [ ] }, // ARIA owned, t1_button
+ { SECTION: [ ] }, // ARIA owned, t1_subdiv
+ { GROUPING: [ ] } // ARIA owned, t1_group
+ ]
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================ Remove element ======================================== */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ yield ContentTask.spawn(browser, {}, () =>
+ content.document.getElementById('t1_span').parentNode.removeChild(
+ content.document.getElementById('t1_span')));
+ yield onReorder;
+
+ // subdiv should go away
+ tree = {
+ SECTION: [
+ { CHECKBUTTON: [ ] }, // explicit, t1_checkbox
+ { RADIOBUTTON: [ ] }, // explicit, t1_child3
+ { PUSHBUTTON: [ ] }, // ARIA owned, t1_button
+ { GROUPING: [ ] } // ARIA owned, t1_group
+ ]
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================ Remove ID ============================================= */
+ onReorder = waitForEvent(EVENT_REORDER, docID);
+ yield invokeSetAttribute(browser, 't1_group', 'id');
+ yield onReorder;
+
+ tree = {
+ SECTION: [
+ { CHECKBUTTON: [ ] },
+ { RADIOBUTTON: [ ] },
+ { PUSHBUTTON: [ ] } // ARIA owned, t1_button
+ ]
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================ Set ID ================================================ */
+ onReorder = waitForEvent(EVENT_REORDER, docID);
+ yield invokeSetAttribute(browser, 't1_grouptmp', 'id', 't1_group');
+ yield onReorder;
+
+ tree = {
+ SECTION: [
+ { CHECKBUTTON: [ ] },
+ { RADIOBUTTON: [ ] },
+ { PUSHBUTTON: [ ] }, // ARIA owned, t1_button
+ { GROUPING: [ ] } // ARIA owned, t1_group, previously t1_grouptmp
+ ]
+ };
+ testAccessibleTree(acc, tree);
+}
+
+function* removeContainer(browser, accDoc) {
+ const id = 't2_container1';
+ const acc = findAccessibleChildByID(accDoc, id);
+
+ let tree = {
+ SECTION: [
+ { CHECKBUTTON: [ ] } // ARIA owned, 't2_owned'
+ ]
+ };
+ testAccessibleTree(acc, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, id);
+ yield ContentTask.spawn(browser, {}, () =>
+ content.document.getElementById('t2_container2').removeChild(
+ content.document.getElementById('t2_container3')));
+ yield onReorder;
+
+ tree = {
+ SECTION: [ ]
+ };
+ testAccessibleTree(acc, tree);
+}
+
+function* stealAndRecacheChildren(browser, accDoc) {
+ const id1 = 't3_container1';
+ const id2 = 't3_container2';
+ const acc1 = findAccessibleChildByID(accDoc, id1);
+ const acc2 = findAccessibleChildByID(accDoc, id2);
+
+ /* ================ Steal from other ARIA owns ============================ */
+ let onReorder = waitForEvent(EVENT_REORDER, id2);
+ yield invokeSetAttribute(browser, id2, 'aria-owns', 't3_child');
+ yield onReorder;
+
+ let tree = {
+ SECTION: [ ]
+ };
+ testAccessibleTree(acc1, tree);
+
+ tree = {
+ SECTION: [
+ { CHECKBUTTON: [ ] }
+ ]
+ };
+ testAccessibleTree(acc2, tree);
+
+ /* ================ Append element to recache children ==================== */
+ onReorder = waitForEvent(EVENT_REORDER, id2);
+ yield ContentTask.spawn(browser, id2, id => {
+ let div = content.document.createElement('div');
+ div.setAttribute('role', 'radio');
+ content.document.getElementById(id).appendChild(div);
+ });
+ yield onReorder;
+
+ tree = {
+ SECTION: [ ]
+ };
+ testAccessibleTree(acc1, tree);
+
+ tree = {
+ SECTION: [
+ { RADIOBUTTON: [ ] },
+ { CHECKBUTTON: [ ] } // ARIA owned
+ ]
+ };
+ testAccessibleTree(acc2, tree);
+}
+
+function* showHiddenElement(browser, accDoc) {
+ const id = 't4_container1';
+ const acc = findAccessibleChildByID(accDoc, id);
+
+ let tree = {
+ SECTION: [
+ { RADIOBUTTON: [] }
+ ]
+ };
+ testAccessibleTree(acc, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, id);
+ yield invokeSetStyle(browser, 't4_child1', 'display', 'block');
+ yield onReorder;
+
+ tree = {
+ SECTION: [
+ { CHECKBUTTON: [] },
+ { RADIOBUTTON: [] }
+ ]
+ };
+ testAccessibleTree(acc, tree);
+}
+
+function* rearrangeARIAOwns(browser, accDoc) {
+ const id = 't5_container';
+ const acc = findAccessibleChildByID(accDoc, id);
+ const tests = [{
+ val: 't5_checkbox t5_radio t5_button',
+ roleList: [ 'CHECKBUTTON', 'RADIOBUTTON', 'PUSHBUTTON' ]
+ }, {
+ val: 't5_radio t5_button t5_checkbox',
+ roleList: [ 'RADIOBUTTON', 'PUSHBUTTON', 'CHECKBUTTON' ]
+ }];
+
+ for (let { val, roleList } of tests) {
+ let onReorder = waitForEvent(EVENT_REORDER, id);
+ yield invokeSetAttribute(browser, id, 'aria-owns', val);
+ yield onReorder;
+
+ let tree = { SECTION: [ ] };
+ for (let role of roleList) {
+ let ch = {};
+ ch[role] = [];
+ tree.SECTION.push(ch);
+ }
+ testAccessibleTree(acc, tree);
+ }
+}
+
+function* removeNotARIAOwnedEl(browser, accDoc) {
+ const id = 't6_container';
+ const acc = findAccessibleChildByID(accDoc, id);
+
+ let tree = {
+ SECTION: [
+ { TEXT_LEAF: [ ] },
+ { GROUPING: [ ] }
+ ]
+ };
+ testAccessibleTree(acc, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, id);
+ yield ContentTask.spawn(browser, id, contentId => {
+ content.document.getElementById(contentId).removeChild(
+ content.document.getElementById('t6_span'));
+ });
+ yield onReorder;
+
+ tree = {
+ SECTION: [
+ { GROUPING: [ ] }
+ ]
+ };
+ testAccessibleTree(acc, tree);
+}
+
+addAccessibleTask('doc_treeupdate_ariaowns.html', function*(browser, accDoc) {
+ yield testContainer1(browser, accDoc);
+ yield removeContainer(browser, accDoc);
+ yield stealAndRecacheChildren(browser, accDoc);
+ yield showHiddenElement(browser, accDoc);
+ yield rearrangeARIAOwns(browser, accDoc);
+ yield removeNotARIAOwnedEl(browser, accDoc);
+});
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_canvas.js b/accessible/tests/browser/e10s/browser_treeupdate_canvas.js
new file mode 100644
index 000000000..e4b3b70f7
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_canvas.js
@@ -0,0 +1,25 @@
+/* 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/. */
+
+'use strict';
+
+/* global EVENT_SHOW */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+addAccessibleTask(`
+ <canvas id="canvas">
+ <div id="dialog" role="dialog" style="display: none;"></div>
+ </canvas>`, function*(browser, accDoc) {
+ let canvas = findAccessibleChildByID(accDoc, 'canvas');
+ let dialog = findAccessibleChildByID(accDoc, 'dialog');
+
+ testAccessibleTree(canvas, { CANVAS: [] });
+
+ let onShow = waitForEvent(EVENT_SHOW, 'dialog');
+ yield invokeSetStyle(browser, 'dialog', 'display', 'block');
+ yield onShow;
+
+ testAccessibleTree(dialog, { DIALOG: [] });
+});
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_cssoverflow.js b/accessible/tests/browser/e10s/browser_treeupdate_cssoverflow.js
new file mode 100644
index 000000000..d8b217380
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_cssoverflow.js
@@ -0,0 +1,64 @@
+/* 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/. */
+
+'use strict';
+
+/* global EVENT_REORDER */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+addAccessibleTask(`
+ <div id="container"><div id="scrollarea" style="overflow:auto;"><input>
+ </div></div>
+ <div id="container2"><div id="scrollarea2" style="overflow:hidden;">
+ </div></div>`, function*(browser, accDoc) {
+ const id1 = 'container';
+ const id2 = 'container2';
+ const container = findAccessibleChildByID(accDoc, id1);
+ const container2 = findAccessibleChildByID(accDoc, id2);
+
+ /* ================= Change scroll range ================================== */
+ let tree = {
+ SECTION: [ {// container
+ SECTION: [ {// scroll area
+ ENTRY: [ ] // child content
+ } ]
+ } ]
+ };
+ testAccessibleTree(container, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, id1);
+ yield ContentTask.spawn(browser, id1, id => {
+ let doc = content.document;
+ doc.getElementById('scrollarea').style.width = '20px';
+ doc.getElementById(id).appendChild(doc.createElement('input'));
+ });
+ yield onReorder;
+
+ tree = {
+ SECTION: [ {// container
+ SECTION: [ {// scroll area
+ ENTRY: [ ] // child content
+ } ]
+ }, {
+ ENTRY: [ ] // inserted input
+ } ]
+ };
+ testAccessibleTree(container, tree);
+
+ /* ================= Change scrollbar styles ============================== */
+ tree = { SECTION: [ ] };
+ testAccessibleTree(container2, tree);
+
+ onReorder = waitForEvent(EVENT_REORDER, id2);
+ yield invokeSetStyle(browser, 'scrollarea2', 'overflow', 'auto');
+ yield onReorder;
+
+ tree = {
+ SECTION: [ // container
+ { SECTION: [] } // scroll area
+ ]
+ };
+ testAccessibleTree(container2, tree);
+});
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_doc.js b/accessible/tests/browser/e10s/browser_treeupdate_doc.js
new file mode 100644
index 000000000..ccb1d1566
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_doc.js
@@ -0,0 +1,312 @@
+/* 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/. */
+
+'use strict';
+
+/* global ROLE_PUSHBUTTON, ROLE_TEXT_LEAF, EVENT_REORDER, ROLE_DOCUMENT,
+ nsIAccessibleDocument */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+const iframeSrc = `data:text/html,
+ <html>
+ <head>
+ <meta charset='utf-8'/>
+ <title>Inner Iframe</title>
+ </head>
+ <body id='inner-iframe'></body>
+ </html>`;
+
+addAccessibleTask(`
+ <iframe id="iframe" src="${iframeSrc}"></iframe>`, function*(browser, accDoc) {
+ // ID of the iframe that is being tested
+ const id = 'inner-iframe';
+
+ let iframe = findAccessibleChildByID(accDoc, id);
+
+ /* ================= Initial tree check =================================== */
+ let tree = {
+ role: ROLE_DOCUMENT,
+ children: [ ]
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Write iframe document ================================ */
+ let reorderEventPromise = waitForEvent(EVENT_REORDER, id);
+ yield ContentTask.spawn(browser, id, contentId => {
+ let docNode = content.document.getElementById('iframe').contentDocument;
+ let newHTMLNode = docNode.createElement('html');
+ let newBodyNode = docNode.createElement('body');
+ let newTextNode = docNode.createTextNode('New Wave');
+ newBodyNode.id = contentId;
+ newBodyNode.appendChild(newTextNode);
+ newHTMLNode.appendChild(newBodyNode);
+ docNode.replaceChild(newHTMLNode, docNode.documentElement);
+ });
+ yield reorderEventPromise;
+
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: 'New Wave'
+ }
+ ]
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Replace iframe HTML element ========================== */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, id);
+ yield ContentTask.spawn(browser, id, contentId => {
+ let docNode = content.document.getElementById('iframe').contentDocument;
+ // We can't use open/write/close outside of iframe document because of
+ // security error.
+ let script = docNode.createElement('script');
+ script.textContent = `
+ document.open();
+ document.write('<body id="${contentId}">hello</body>');
+ document.close();`;
+ docNode.body.appendChild(script);
+ });
+ yield reorderEventPromise;
+
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: 'hello'
+ }
+ ]
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Replace iframe body ================================== */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, id);
+ yield ContentTask.spawn(browser, id, contentId => {
+ let docNode = content.document.getElementById('iframe').contentDocument;
+ let newBodyNode = docNode.createElement('body');
+ let newTextNode = docNode.createTextNode('New Hello');
+ newBodyNode.id = contentId;
+ newBodyNode.appendChild(newTextNode);
+ newBodyNode.setAttribute('role', 'button');
+ docNode.documentElement.replaceChild(newBodyNode, docNode.body);
+ });
+ yield reorderEventPromise;
+
+ tree = {
+ role: ROLE_PUSHBUTTON,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: 'New Hello'
+ }
+ ]
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Open iframe document ================================= */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, id);
+ yield ContentTask.spawn(browser, id, contentId => {
+ // Open document.
+ let docNode = content.document.getElementById('iframe').contentDocument;
+ let script = docNode.createElement('script');
+ script.textContent = `
+ function closeMe() {
+ document.write('Works?');
+ document.close();
+ }
+ window.closeMe = closeMe;
+ document.open();
+ document.write('<body id="${contentId}"></body>');`;
+ docNode.body.appendChild(script);
+ });
+ yield reorderEventPromise;
+
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [ ]
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Close iframe document ================================ */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, id);
+ yield ContentTask.spawn(browser, {}, () => {
+ // Write and close document.
+ let docNode = content.document.getElementById('iframe').contentDocument;
+ docNode.write('Works?');
+ docNode.close();
+ });
+ yield reorderEventPromise;
+
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: 'Works?'
+ }
+ ]
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Remove HTML from iframe document ===================== */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, iframe);
+ yield ContentTask.spawn(browser, {}, () => {
+ // Remove HTML element.
+ let docNode = content.document.getElementById('iframe').contentDocument;
+ docNode.removeChild(docNode.firstChild);
+ });
+ let event = yield reorderEventPromise;
+
+ ok(event.accessible instanceof nsIAccessibleDocument,
+ 'Reorder should happen on the document');
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [ ]
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Insert HTML to iframe document ======================= */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, id);
+ yield ContentTask.spawn(browser, id, contentId => {
+ // Insert HTML element.
+ let docNode = content.document.getElementById('iframe').contentDocument;
+ let html = docNode.createElement('html');
+ let body = docNode.createElement('body');
+ let text = docNode.createTextNode('Haha');
+ body.appendChild(text);
+ body.id = contentId;
+ html.appendChild(body);
+ docNode.appendChild(html);
+ });
+ yield reorderEventPromise;
+
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: 'Haha'
+ }
+ ]
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Remove body from iframe document ===================== */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, iframe);
+ yield ContentTask.spawn(browser, {}, () => {
+ // Remove body element.
+ let docNode = content.document.getElementById('iframe').contentDocument;
+ docNode.documentElement.removeChild(docNode.body);
+ });
+ event = yield reorderEventPromise;
+
+ ok(event.accessible instanceof nsIAccessibleDocument,
+ 'Reorder should happen on the document');
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [ ]
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================ Insert element under document element while body missed */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, iframe);
+ yield ContentTask.spawn(browser, {}, () => {
+ let docNode = content.document.getElementById('iframe').contentDocument;
+ let inputNode = content.window.inputNode = docNode.createElement('input');
+ docNode.documentElement.appendChild(inputNode);
+ });
+ event = yield reorderEventPromise;
+
+ ok(event.accessible instanceof nsIAccessibleDocument,
+ 'Reorder should happen on the document');
+ tree = {
+ DOCUMENT: [
+ { ENTRY: [ ] }
+ ]
+ };
+ testAccessibleTree(iframe, tree);
+
+ reorderEventPromise = waitForEvent(EVENT_REORDER, iframe);
+ yield ContentTask.spawn(browser, {}, () => {
+ let docEl =
+ content.document.getElementById('iframe').contentDocument.documentElement;
+ // Remove aftermath of this test before next test starts.
+ docEl.removeChild(docEl.firstChild);
+ });
+ // Make sure reorder event was fired and that the input was removed.
+ yield reorderEventPromise;
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [ ]
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Insert body to iframe document ======================= */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, id);
+ yield ContentTask.spawn(browser, id, contentId => {
+ // Write and close document.
+ let docNode = content.document.getElementById('iframe').contentDocument;
+ // Insert body element.
+ let body = docNode.createElement('body');
+ let text = docNode.createTextNode('Yo ho ho i butylka roma!');
+ body.appendChild(text);
+ body.id = contentId;
+ docNode.documentElement.appendChild(body);
+ });
+ yield reorderEventPromise;
+
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: 'Yo ho ho i butylka roma!'
+ }
+ ]
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Change source ======================================== */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, 'iframe');
+ yield invokeSetAttribute(browser, 'iframe', 'src',
+ `data:text/html,<html><body id="${id}"><input></body></html>`);
+ event = yield reorderEventPromise;
+
+ tree = {
+ INTERNAL_FRAME: [
+ { DOCUMENT: [
+ { ENTRY: [ ] }
+ ] }
+ ]
+ };
+ testAccessibleTree(event.accessible, tree);
+ iframe = findAccessibleChildByID(event.accessible, id);
+
+ /* ================= Replace iframe body on ARIA role body ================ */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, id);
+ yield ContentTask.spawn(browser, id, contentId => {
+ let docNode = content.document.getElementById('iframe').contentDocument;
+ let newBodyNode = docNode.createElement('body');
+ let newTextNode = docNode.createTextNode('New Hello');
+ newBodyNode.appendChild(newTextNode);
+ newBodyNode.setAttribute('role', 'button');
+ newBodyNode.id = contentId;
+ docNode.documentElement.replaceChild(newBodyNode, docNode.body);
+ });
+ yield reorderEventPromise;
+
+ tree = {
+ role: ROLE_PUSHBUTTON,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: 'New Hello'
+ }
+ ]
+ };
+ testAccessibleTree(iframe, tree);
+});
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_gencontent.js b/accessible/tests/browser/e10s/browser_treeupdate_gencontent.js
new file mode 100644
index 000000000..126419288
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_gencontent.js
@@ -0,0 +1,78 @@
+/* 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/. */
+
+'use strict';
+
+/* global EVENT_REORDER */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+addAccessibleTask(`
+ <style>
+ .gentext:before {
+ content: "START"
+ }
+ .gentext:after {
+ content: "END"
+ }
+ </style>
+ <div id="container1"></div>
+ <div id="container2"><div id="container2_child">text</div></div>`,
+ function*(browser, accDoc) {
+ const id1 = 'container1';
+ const id2 = 'container2';
+ let container1 = findAccessibleChildByID(accDoc, id1);
+ let container2 = findAccessibleChildByID(accDoc, id2);
+
+ let tree = {
+ SECTION: [ ] // container
+ };
+ testAccessibleTree(container1, tree);
+
+ tree = {
+ SECTION: [ { // container2
+ SECTION: [ { // container2 child
+ TEXT_LEAF: [ ] // primary text
+ } ]
+ } ]
+ };
+ testAccessibleTree(container2, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, id1);
+ // Create and add an element with CSS generated content to container1
+ yield ContentTask.spawn(browser, id1, id => {
+ let node = content.document.createElement('div');
+ node.textContent = 'text';
+ node.setAttribute('class', 'gentext');
+ content.document.getElementById(id).appendChild(node);
+ });
+ yield onReorder;
+
+ tree = {
+ SECTION: [ // container
+ { SECTION: [ // inserted node
+ { STATICTEXT: [] }, // :before
+ { TEXT_LEAF: [] }, // primary text
+ { STATICTEXT: [] } // :after
+ ] }
+ ]
+ };
+ testAccessibleTree(container1, tree);
+
+ onReorder = waitForEvent(EVENT_REORDER, id2);
+ // Add CSS generated content to an element in container2's subtree
+ yield invokeSetAttribute(browser, 'container2_child', 'class', 'gentext');
+ yield onReorder;
+
+ tree = {
+ SECTION: [ // container2
+ { SECTION: [ // container2 child
+ { STATICTEXT: [] }, // :before
+ { TEXT_LEAF: [] }, // primary text
+ { STATICTEXT: [] } // :after
+ ] }
+ ]
+ };
+ testAccessibleTree(container2, tree);
+ });
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_hidden.js b/accessible/tests/browser/e10s/browser_treeupdate_hidden.js
new file mode 100644
index 000000000..00369ec05
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_hidden.js
@@ -0,0 +1,30 @@
+/* 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/. */
+
+'use strict';
+
+/* global EVENT_REORDER */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+function* setHidden(browser, value) {
+ let onReorder = waitForEvent(EVENT_REORDER, 'container');
+ yield invokeSetAttribute(browser, 'child', 'hidden', value);
+ yield onReorder;
+}
+
+addAccessibleTask('<div id="container"><input id="child"></div>',
+ function*(browser, accDoc) {
+ let container = findAccessibleChildByID(accDoc, 'container');
+
+ testAccessibleTree(container, { SECTION: [ { ENTRY: [ ] } ] });
+
+ // Set @hidden attribute
+ yield setHidden(browser, 'true');
+ testAccessibleTree(container, { SECTION: [ ] });
+
+ // Remove @hidden attribute
+ yield setHidden(browser);
+ testAccessibleTree(container, { SECTION: [ { ENTRY: [ ] } ] });
+ });
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_imagemap.js b/accessible/tests/browser/e10s/browser_treeupdate_imagemap.js
new file mode 100644
index 000000000..d299c0acb
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_imagemap.js
@@ -0,0 +1,176 @@
+/* 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/. */
+
+'use strict';
+
+/* global EVENT_REORDER, ROLE_LINK */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+function* testImageMap(browser, accDoc) {
+ const id = 'imgmap';
+ const acc = findAccessibleChildByID(accDoc, id);
+
+ /* ================= Initial tree test ==================================== */
+ let tree = {
+ IMAGE_MAP: [
+ { role: ROLE_LINK, name: 'b', children: [ ] }
+ ]
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================= Insert area ========================================== */
+ let onReorder = waitForEvent(EVENT_REORDER, id);
+ yield ContentTask.spawn(browser, {}, () => {
+ let areaElm = content.document.createElement('area');
+ let mapNode = content.document.getElementById('map');
+ areaElm.setAttribute('href',
+ 'http://www.bbc.co.uk/radio4/atoz/index.shtml#a');
+ areaElm.setAttribute('coords', '0,0,13,14');
+ areaElm.setAttribute('alt', 'a');
+ areaElm.setAttribute('shape', 'rect');
+ mapNode.insertBefore(areaElm, mapNode.firstChild);
+ });
+ yield onReorder;
+
+ tree = {
+ IMAGE_MAP: [
+ { role: ROLE_LINK, name: 'a', children: [ ] },
+ { role: ROLE_LINK, name: 'b', children: [ ] }
+ ]
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================= Append area ========================================== */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ yield ContentTask.spawn(browser, {}, () => {
+ let areaElm = content.document.createElement('area');
+ let mapNode = content.document.getElementById('map');
+ areaElm.setAttribute('href',
+ 'http://www.bbc.co.uk/radio4/atoz/index.shtml#c');
+ areaElm.setAttribute('coords', '34,0,47,14');
+ areaElm.setAttribute('alt', 'c');
+ areaElm.setAttribute('shape', 'rect');
+ mapNode.appendChild(areaElm);
+ });
+ yield onReorder;
+
+ tree = {
+ IMAGE_MAP: [
+ { role: ROLE_LINK, name: 'a', children: [ ] },
+ { role: ROLE_LINK, name: 'b', children: [ ] },
+ { role: ROLE_LINK, name: 'c', children: [ ] }
+ ]
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================= Remove area ========================================== */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ yield ContentTask.spawn(browser, {}, () => {
+ let mapNode = content.document.getElementById('map');
+ mapNode.removeChild(mapNode.firstElementChild);
+ });
+ yield onReorder;
+
+ tree = {
+ IMAGE_MAP: [
+ { role: ROLE_LINK, name: 'b', children: [ ] },
+ { role: ROLE_LINK, name: 'c', children: [ ] }
+ ]
+ };
+ testAccessibleTree(acc, tree);
+}
+
+function* testContainer(browser) {
+ const id = 'container';
+ /* ================= Remove name on map =================================== */
+ let onReorder = waitForEvent(EVENT_REORDER, id);
+ yield invokeSetAttribute(browser, 'map', 'name');
+ let event = yield onReorder;
+ const acc = event.accessible;
+
+ let tree = {
+ SECTION: [
+ { GRAPHIC: [ ] }
+ ]
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================= Restore name on map ================================== */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ yield invokeSetAttribute(browser, 'map', 'name', 'atoz_map');
+ // XXX: force repainting of the image (see bug 745788 for details).
+ yield BrowserTestUtils.synthesizeMouse('#imgmap', 10, 10,
+ { type: 'mousemove' }, browser);
+ yield onReorder;
+
+ tree = {
+ SECTION: [ {
+ IMAGE_MAP: [
+ { LINK: [ ] },
+ { LINK: [ ] }
+ ]
+ } ]
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================= Remove map =========================================== */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ yield ContentTask.spawn(browser, {}, () => {
+ let mapNode = content.document.getElementById('map');
+ mapNode.parentNode.removeChild(mapNode);
+ });
+ yield onReorder;
+
+ tree = {
+ SECTION: [
+ { GRAPHIC: [ ] }
+ ]
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================= Insert map =========================================== */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ yield ContentTask.spawn(browser, id, contentId => {
+ let map = content.document.createElement('map');
+ let area = content.document.createElement('area');
+
+ map.setAttribute('name', 'atoz_map');
+ map.setAttribute('id', 'map');
+
+ area.setAttribute('href',
+ 'http://www.bbc.co.uk/radio4/atoz/index.shtml#b');
+ area.setAttribute('coords', '17,0,30,14');
+ area.setAttribute('alt', 'b');
+ area.setAttribute('shape', 'rect');
+
+ map.appendChild(area);
+ content.document.getElementById(contentId).appendChild(map);
+ });
+ yield onReorder;
+
+ tree = {
+ SECTION: [ {
+ IMAGE_MAP: [
+ { LINK: [ ] }
+ ]
+ } ]
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================= Hide image map ======================================= */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ yield invokeSetStyle(browser, 'imgmap', 'display', 'none');
+ yield onReorder;
+
+ tree = {
+ SECTION: [ ]
+ };
+ testAccessibleTree(acc, tree);
+}
+
+addAccessibleTask('doc_treeupdate_imagemap.html', function*(browser, accDoc) {
+ yield testImageMap(browser, accDoc);
+ yield testContainer(browser);
+});
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_list.js b/accessible/tests/browser/e10s/browser_treeupdate_list.js
new file mode 100644
index 000000000..023adf8e3
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_list.js
@@ -0,0 +1,43 @@
+/* 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/. */
+
+'use strict';
+
+/* global ROLE_TEXT_LEAF, EVENT_REORDER, ROLE_STATICTEXT, ROLE_LISTITEM */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+function* setDisplayAndWaitForReorder(browser, value) {
+ let onReorder = waitForEvent(EVENT_REORDER, 'ul');
+ yield invokeSetStyle(browser, 'li', 'display', value);
+ return yield onReorder;
+}
+
+addAccessibleTask(`
+ <ul id="ul">
+ <li id="li">item1</li>
+ </ul>`, function*(browser, accDoc) {
+ let li = findAccessibleChildByID(accDoc, 'li');
+ let bullet = li.firstChild;
+ let accTree = {
+ role: ROLE_LISTITEM,
+ children: [ {
+ role: ROLE_STATICTEXT,
+ children: []
+ }, {
+ role: ROLE_TEXT_LEAF,
+ children: []
+ } ]
+ };
+ testAccessibleTree(li, accTree);
+
+ yield setDisplayAndWaitForReorder(browser, 'none');
+
+ ok(isDefunct(li), 'Check that li is defunct.');
+ ok(isDefunct(bullet), 'Check that bullet is defunct.');
+
+ let event = yield setDisplayAndWaitForReorder(browser, 'list-item');
+
+ testAccessibleTree(findAccessibleChildByID(event.accessible, 'li'), accTree);
+});
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_list_editabledoc.js b/accessible/tests/browser/e10s/browser_treeupdate_list_editabledoc.js
new file mode 100644
index 000000000..7b01af87a
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_list_editabledoc.js
@@ -0,0 +1,39 @@
+/* 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/. */
+
+'use strict';
+
+/* global ROLE_TEXT_LEAF, EVENT_REORDER, ROLE_LISTITEM, ROLE_LIST,
+ ROLE_STATICTEXT */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+addAccessibleTask('<ol id="list"></ol>', function*(browser, accDoc) {
+ let list = findAccessibleChildByID(accDoc, 'list');
+
+ testAccessibleTree(list, {
+ role: ROLE_LIST,
+ children: [ ]
+ });
+
+ yield invokeSetAttribute(browser, 'body', 'contentEditable', 'true');
+ let onReorder = waitForEvent(EVENT_REORDER, 'list');
+ yield ContentTask.spawn(browser, {}, () => {
+ let li = content.document.createElement('li');
+ li.textContent = 'item';
+ content.document.getElementById('list').appendChild(li);
+ });
+ yield onReorder;
+
+ testAccessibleTree(list, {
+ role: ROLE_LIST,
+ children: [ {
+ role: ROLE_LISTITEM,
+ children: [
+ { role: ROLE_STATICTEXT, name: "1. ", children: [] },
+ { role: ROLE_TEXT_LEAF, children: [] }
+ ]
+ } ]
+ });
+});
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_listener.js b/accessible/tests/browser/e10s/browser_treeupdate_listener.js
new file mode 100644
index 000000000..7b7880312
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_listener.js
@@ -0,0 +1,29 @@
+/* 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/. */
+
+'use strict';
+
+/* global EVENT_REORDER */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+addAccessibleTask('<span id="parent"><span id="child"></span></span>',
+ function*(browser, accDoc) {
+ is(findAccessibleChildByID(accDoc, 'parent'), null,
+ 'Check that parent is not accessible.');
+ is(findAccessibleChildByID(accDoc, 'child'), null,
+ 'Check that child is not accessible.');
+
+ let onReorder = waitForEvent(EVENT_REORDER, 'body');
+ // Add an event listener to parent.
+ yield ContentTask.spawn(browser, {}, () => {
+ content.window.dummyListener = () => {};
+ content.document.getElementById('parent').addEventListener(
+ 'click', content.window.dummyListener);
+ });
+ yield onReorder;
+
+ let tree = { TEXT: [] };
+ testAccessibleTree(findAccessibleChildByID(accDoc, 'parent'), tree);
+ });
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_optgroup.js b/accessible/tests/browser/e10s/browser_treeupdate_optgroup.js
new file mode 100644
index 000000000..45001afaa
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_optgroup.js
@@ -0,0 +1,91 @@
+/* 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/. */
+
+'use strict';
+
+/* global EVENT_REORDER */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+addAccessibleTask('<select id="select"></select>', function*(browser, accDoc) {
+ let select = findAccessibleChildByID(accDoc, 'select');
+
+ let onEvent = waitForEvent(EVENT_REORDER, 'select');
+ // Create a combobox with grouping and 2 standalone options
+ yield ContentTask.spawn(browser, {}, () => {
+ let doc = content.document;
+ let contentSelect = doc.getElementById('select');
+ let optGroup = doc.createElement('optgroup');
+
+ for (let i = 0; i < 2; i++) {
+ let opt = doc.createElement('option');
+ opt.value = i;
+ opt.text = 'Option: Value ' + i;
+ optGroup.appendChild(opt);
+ }
+ contentSelect.add(optGroup, null);
+
+ for (let i = 0; i < 2; i++) {
+ let opt = doc.createElement('option');
+ contentSelect.add(opt, null);
+ }
+ contentSelect.firstChild.firstChild.id = 'option1Node';
+ });
+ let event = yield onEvent;
+ let option1Node = findAccessibleChildByID(event.accessible, 'option1Node');
+
+ let tree = {
+ COMBOBOX: [ {
+ COMBOBOX_LIST: [ {
+ GROUPING: [
+ { COMBOBOX_OPTION: [ { TEXT_LEAF: [] } ] },
+ { COMBOBOX_OPTION: [ { TEXT_LEAF: [] } ] }
+ ]
+ }, {
+ COMBOBOX_OPTION: []
+ }, {
+ COMBOBOX_OPTION: []
+ } ]
+ } ]
+ };
+ testAccessibleTree(select, tree);
+ ok(!isDefunct(option1Node), 'option shouldn\'t be defunct');
+
+ onEvent = waitForEvent(EVENT_REORDER, 'select');
+ // Remove grouping from combobox
+ yield ContentTask.spawn(browser, {}, () => {
+ let contentSelect = content.document.getElementById('select');
+ contentSelect.removeChild(contentSelect.firstChild);
+ });
+ yield onEvent;
+
+ tree = {
+ COMBOBOX: [ {
+ COMBOBOX_LIST: [
+ { COMBOBOX_OPTION: [] },
+ { COMBOBOX_OPTION: [] }
+ ]
+ } ]
+ };
+ testAccessibleTree(select, tree);
+ ok(isDefunct(option1Node),
+ 'removed option shouldn\'t be accessible anymore!');
+
+ onEvent = waitForEvent(EVENT_REORDER, 'select');
+ // Remove all options from combobox
+ yield ContentTask.spawn(browser, {}, () => {
+ let contentSelect = content.document.getElementById('select');
+ while (contentSelect.length) {
+ contentSelect.remove(0);
+ }
+ });
+ yield onEvent;
+
+ tree = {
+ COMBOBOX: [ {
+ COMBOBOX_LIST: [ ]
+ } ]
+ };
+ testAccessibleTree(select, tree);
+});
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_removal.js b/accessible/tests/browser/e10s/browser_treeupdate_removal.js
new file mode 100644
index 000000000..9892bbcd6
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_removal.js
@@ -0,0 +1,39 @@
+/* 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/. */
+
+'use strict';
+
+/* global EVENT_REORDER */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+addAccessibleTask('doc_treeupdate_removal.xhtml', function*(browser, accDoc) {
+ ok(isAccessible(findAccessibleChildByID(accDoc, 'the_table')),
+ 'table should be accessible');
+
+ // Move the_table element into hidden subtree.
+ let onReorder = waitForEvent(EVENT_REORDER, 'body');
+ yield ContentTask.spawn(browser, {}, () => content.document.getElementById(
+ 'the_displaynone').appendChild(content.document.getElementById(
+ 'the_table')));
+ yield onReorder;
+
+ ok(!isAccessible(findAccessibleChildByID(accDoc, 'the_table')),
+ 'table in display none tree shouldn\'t be accessible');
+ ok(!isAccessible(findAccessibleChildByID(accDoc, 'the_row')),
+ 'row shouldn\'t be accessible');
+
+ // Remove the_row element (since it did not have accessible, no event needed).
+ yield ContentTask.spawn(browser, {}, () =>
+ content.document.body.removeChild(
+ content.document.getElementById('the_row')));
+
+ // make sure no accessibles have stuck around.
+ ok(!isAccessible(findAccessibleChildByID(accDoc, 'the_row')),
+ 'row shouldn\'t be accessible');
+ ok(!isAccessible(findAccessibleChildByID(accDoc, 'the_table')),
+ 'table shouldn\'t be accessible');
+ ok(!isAccessible(findAccessibleChildByID(accDoc, 'the_displayNone')),
+ 'display none things shouldn\'t be accessible');
+});
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_table.js b/accessible/tests/browser/e10s/browser_treeupdate_table.js
new file mode 100644
index 000000000..9609f51ac
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_table.js
@@ -0,0 +1,51 @@
+/* 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/. */
+
+'use strict';
+
+/* global EVENT_REORDER */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+addAccessibleTask(`
+ <table id="table">
+ <tr>
+ <td>cell1</td>
+ <td>cell2</td>
+ </tr>
+ </table>`, function*(browser, accDoc) {
+ let table = findAccessibleChildByID(accDoc, 'table');
+
+ let tree = {
+ TABLE: [
+ { ROW: [
+ { CELL: [ {TEXT_LEAF: [] }]},
+ { CELL: [ {TEXT_LEAF: [] }]}
+ ] }
+ ]
+ };
+ testAccessibleTree(table, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, 'table');
+ yield ContentTask.spawn(browser, {}, () => {
+ // append a caption, it should appear as a first element in the
+ // accessible tree.
+ let doc = content.document;
+ let caption = doc.createElement('caption');
+ caption.textContent = 'table caption';
+ doc.getElementById('table').appendChild(caption);
+ });
+ yield onReorder;
+
+ tree = {
+ TABLE: [
+ { CAPTION: [ { TEXT_LEAF: [] } ] },
+ { ROW: [
+ { CELL: [ {TEXT_LEAF: [] }]},
+ { CELL: [ {TEXT_LEAF: [] }]}
+ ] }
+ ]
+ };
+ testAccessibleTree(table, tree);
+});
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_textleaf.js b/accessible/tests/browser/e10s/browser_treeupdate_textleaf.js
new file mode 100644
index 000000000..da45e2bc9
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_textleaf.js
@@ -0,0 +1,35 @@
+/* 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/. */
+
+'use strict';
+
+/* global EVENT_REORDER, ROLE_TEXT_CONTAINER ROLE_PARAGRAPH, ROLE_TEXT_LEAF */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+function* removeTextData(browser, accessible, id, role) {
+ let tree = {
+ role: role,
+ children: [ { role: ROLE_TEXT_LEAF, name: "text" } ]
+ };
+ testAccessibleTree(accessible, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, id);
+ yield ContentTask.spawn(browser, id, contentId => {
+ content.document.getElementById(contentId).firstChild.textContent = '';
+ });
+ yield onReorder;
+
+ tree = { role: role, children: [] };
+ testAccessibleTree(accessible, tree);
+}
+
+addAccessibleTask(`
+ <p id="p">text</p>
+ <pre id="pre">text</pre>`, function*(browser, accDoc) {
+ let p = findAccessibleChildByID(accDoc, 'p');
+ let pre = findAccessibleChildByID(accDoc, 'pre');
+ yield removeTextData(browser, p, 'p', ROLE_PARAGRAPH);
+ yield removeTextData(browser, pre, 'pre', ROLE_TEXT_CONTAINER);
+});
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_visibility.js b/accessible/tests/browser/e10s/browser_treeupdate_visibility.js
new file mode 100644
index 000000000..65a55c914
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_visibility.js
@@ -0,0 +1,196 @@
+/* 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/. */
+
+'use strict';
+
+/* global EVENT_REORDER */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+function* testTreeOnHide(browser, accDoc, containerID, id, before, after) {
+ let acc = findAccessibleChildByID(accDoc, containerID);
+ testAccessibleTree(acc, before);
+
+ let onReorder = waitForEvent(EVENT_REORDER, containerID);
+ yield invokeSetStyle(browser, id, 'visibility', 'hidden');
+ yield onReorder;
+
+ testAccessibleTree(acc, after);
+}
+
+function* test3(browser, accessible) {
+ let tree = {
+ SECTION: [ // container
+ { SECTION: [ // parent
+ { SECTION: [ // child
+ { TEXT_LEAF: [] }
+ ] }
+ ] },
+ { SECTION: [ // parent2
+ { SECTION: [ // child2
+ { TEXT_LEAF: [] }
+ ] }
+ ] }
+ ] };
+ testAccessibleTree(accessible, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, 't3_container');
+ yield ContentTask.spawn(browser, {}, () => {
+ let doc = content.document;
+ doc.getElementById('t3_container').style.color = 'red';
+ doc.getElementById('t3_parent').style.visibility = 'hidden';
+ doc.getElementById('t3_parent2').style.visibility = 'hidden';
+ });
+ yield onReorder;
+
+ tree = {
+ SECTION: [ // container
+ { SECTION: [ // child
+ { TEXT_LEAF: [] }
+ ] },
+ { SECTION: [ // child2
+ { TEXT_LEAF: [] }
+ ] }
+ ] };
+ testAccessibleTree(accessible, tree);
+}
+
+function* test4(browser, accessible) {
+ let tree = {
+ SECTION: [
+ { TABLE: [
+ { ROW: [
+ { CELL: [ ] }
+ ] }
+ ] }
+ ] };
+ testAccessibleTree(accessible, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, 't4_parent');
+ yield ContentTask.spawn(browser, {}, () => {
+ let doc = content.document;
+ doc.getElementById('t4_container').style.color = 'red';
+ doc.getElementById('t4_child').style.visibility = 'visible';
+ });
+ yield onReorder;
+
+ tree = {
+ SECTION: [{
+ TABLE: [{
+ ROW: [{
+ CELL: [{
+ SECTION: [{
+ TEXT_LEAF: []
+ }]
+ }]
+ }]
+ }]
+ }]
+ };
+ testAccessibleTree(accessible, tree);
+}
+
+addAccessibleTask('doc_treeupdate_visibility.html', function*(browser, accDoc) {
+ let t3Container = findAccessibleChildByID(accDoc, 't3_container');
+ let t4Container = findAccessibleChildByID(accDoc, 't4_container');
+
+ yield testTreeOnHide(browser, accDoc, 't1_container', 't1_parent', {
+ SECTION: [{
+ SECTION: [{
+ SECTION: [ { TEXT_LEAF: [] } ]
+ }]
+ }]
+ }, {
+ SECTION: [ {
+ SECTION: [ { TEXT_LEAF: [] } ]
+ } ]
+ });
+
+ yield testTreeOnHide(browser, accDoc, 't2_container', 't2_grandparent', {
+ SECTION: [{ // container
+ SECTION: [{ // grand parent
+ SECTION: [{
+ SECTION: [{ // child
+ TEXT_LEAF: []
+ }]
+ }, {
+ SECTION: [{ // child2
+ TEXT_LEAF: []
+ }]
+ }]
+ }]
+ }]
+ }, {
+ SECTION: [{ // container
+ SECTION: [{ // child
+ TEXT_LEAF: []
+ }]
+ }, {
+ SECTION: [{ // child2
+ TEXT_LEAF: []
+ }]
+ }]
+ });
+
+ yield test3(browser, t3Container);
+ yield test4(browser, t4Container);
+
+ yield testTreeOnHide(browser, accDoc, 't5_container', 't5_subcontainer', {
+ SECTION: [{ // container
+ SECTION: [{ // subcontainer
+ TABLE: [{
+ ROW: [{
+ CELL: [{
+ SECTION: [{ // child
+ TEXT_LEAF: []
+ }]
+ }]
+ }]
+ }]
+ }]
+ }]
+ }, {
+ SECTION: [{ // container
+ SECTION: [{ // child
+ TEXT_LEAF: []
+ }]
+ }]
+ });
+
+ yield testTreeOnHide(browser, accDoc, 't6_container', 't6_subcontainer', {
+ SECTION: [{ // container
+ SECTION: [{ // subcontainer
+ TABLE: [{
+ ROW: [{
+ CELL: [{
+ TABLE: [{ // nested table
+ ROW: [{
+ CELL: [{
+ SECTION: [{ // child
+ TEXT_LEAF: []
+ }]
+ }]
+ }]
+ }]
+ }]
+ }]
+ }]
+ }, {
+ SECTION: [{ // child2
+ TEXT_LEAF: []
+ }]
+ }]
+ }]
+ }, {
+ SECTION: [{ // container
+ SECTION: [{ // child
+ TEXT_LEAF: []
+ }]
+ }, {
+ SECTION: [{ // child2
+ TEXT_LEAF: []
+ }]
+ }]
+ });
+});
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_whitespace.js b/accessible/tests/browser/e10s/browser_treeupdate_whitespace.js
new file mode 100644
index 000000000..c9dbde691
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_whitespace.js
@@ -0,0 +1,71 @@
+/* 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/. */
+
+'use strict';
+
+/* global EVENT_REORDER */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+addAccessibleTask('doc_treeupdate_whitespace.html', function*(browser, accDoc) {
+ let container1 = findAccessibleChildByID(accDoc, 'container1');
+ let container2Parent = findAccessibleChildByID(accDoc, 'container2-parent');
+
+ let tree = {
+ SECTION: [
+ { GRAPHIC: [] },
+ { TEXT_LEAF: [] },
+ { GRAPHIC: [] },
+ { TEXT_LEAF: [] },
+ { GRAPHIC: [] }
+ ]
+ };
+ testAccessibleTree(container1, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, 'container1');
+ // Remove img1 from container1
+ yield ContentTask.spawn(browser, {}, () => {
+ let doc = content.document;
+ doc.getElementById('container1').removeChild(
+ doc.getElementById('img1'));
+ });
+ yield onReorder;
+
+ tree = {
+ SECTION: [
+ { GRAPHIC: [] },
+ { TEXT_LEAF: [] },
+ { GRAPHIC: [] }
+ ]
+ };
+ testAccessibleTree(container1, tree);
+
+ tree = {
+ SECTION: [
+ { LINK: [] },
+ { LINK: [ { GRAPHIC: [] } ] }
+ ]
+ };
+ testAccessibleTree(container2Parent, tree);
+
+ onReorder = waitForEvent(EVENT_REORDER, 'container2-parent');
+ // Append an img with valid src to container2
+ yield ContentTask.spawn(browser, {}, () => {
+ let doc = content.document;
+ let img = doc.createElement('img');
+ img.setAttribute('src',
+ 'http://example.com/a11y/accessible/tests/mochitest/moz.png');
+ doc.getElementById('container2').appendChild(img);
+ });
+ yield onReorder;
+
+ tree = {
+ SECTION: [
+ { LINK: [ { GRAPHIC: [ ] } ] },
+ { TEXT_LEAF: [ ] },
+ { LINK: [ { GRAPHIC: [ ] } ] }
+ ]
+ };
+ testAccessibleTree(container2Parent, tree);
+});
diff --git a/accessible/tests/browser/e10s/doc_treeupdate_ariadialog.html b/accessible/tests/browser/e10s/doc_treeupdate_ariadialog.html
new file mode 100644
index 000000000..9d08854b9
--- /dev/null
+++ b/accessible/tests/browser/e10s/doc_treeupdate_ariadialog.html
@@ -0,0 +1,23 @@
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Tree Update ARIA Dialog Test</title>
+ </head>
+ <body id="body">
+ <div id="dialog" role="dialog" style="display: none;">
+ <table id="table" role="presentation"
+ style="display: block; position: fixed; top: 88px; left: 312.5px; z-index: 10010;">
+ <tbody>
+ <tr>
+ <td role="presentation">
+ <div role="presentation">
+ <a id="a" role="button">text</a>
+ </div>
+ <input id="input">
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </body>
+</html>
diff --git a/accessible/tests/browser/e10s/doc_treeupdate_ariaowns.html b/accessible/tests/browser/e10s/doc_treeupdate_ariaowns.html
new file mode 100644
index 000000000..38b5c333a
--- /dev/null
+++ b/accessible/tests/browser/e10s/doc_treeupdate_ariaowns.html
@@ -0,0 +1,44 @@
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Tree Update ARIA Owns Test</title>
+ </head>
+ <body id="body">
+ <div id="t1_container" aria-owns="t1_checkbox t1_button">
+ <div role="button" id="t1_button"></div>
+ <div role="checkbox" id="t1_checkbox">
+ <span id="t1_span">
+ <div id="t1_subdiv"></div>
+ </span>
+ </div>
+ </div>
+ <div id="t1_group" role="group"></div>
+ <div id="t1_grouptmp" role="group"></div>
+
+ <div id="t2_container1" aria-owns="t2_owned"></div>
+ <div id="t2_container2">
+ <div id="t2_container3"><div id="t2_owned" role="checkbox"></div></div>
+ </div>
+
+ <div id="t3_container1" aria-owns="t3_child"></div>
+ <div id="t3_child" role="checkbox"></div>
+ <div id="t3_container2"></div>
+
+ <div id="t4_container1" aria-owns="t4_child1 t4_child2"></div>
+ <div id="t4_container2">
+ <div id="t4_child1" style="display:none" role="checkbox"></div>
+ <div id="t4_child2" role="radio"></div>
+ </div>
+
+ <div id="t5_container">
+ <div role="button" id="t5_button"></div>
+ <div role="checkbox" id="t5_checkbox"></div>
+ <div role="radio" id="t5_radio"></div>
+ </div>
+
+ <div id="t6_container" aria-owns="t6_fake">
+ <span id="t6_span">hey</span>
+ </div>
+ <div id="t6_fake" role="group"></div>
+ </body>
+</html>
diff --git a/accessible/tests/browser/e10s/doc_treeupdate_imagemap.html b/accessible/tests/browser/e10s/doc_treeupdate_imagemap.html
new file mode 100644
index 000000000..4dd230fc2
--- /dev/null
+++ b/accessible/tests/browser/e10s/doc_treeupdate_imagemap.html
@@ -0,0 +1,21 @@
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Tree Update Imagemap Test</title>
+ </head>
+ <body id="body">
+ <map name="atoz_map" id="map">
+ <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b"
+ coords="17,0,30,14" alt="b" shape="rect">
+ </map>
+
+ <div id="container">
+ <img id="imgmap" width="447" height="15"
+ usemap="#atoz_map"
+ src="http://example.com/a11y/accessible/tests/mochitest/letters.gif"><!--
+ Important: no whitespace between the <img> and the </div>, so we
+ don't end up with textframes there, because those would be reflected
+ in our accessible tree in some cases.
+ --></div>
+ </body>
+</html>
diff --git a/accessible/tests/browser/e10s/doc_treeupdate_removal.xhtml b/accessible/tests/browser/e10s/doc_treeupdate_removal.xhtml
new file mode 100644
index 000000000..9c59fb9d1
--- /dev/null
+++ b/accessible/tests/browser/e10s/doc_treeupdate_removal.xhtml
@@ -0,0 +1,11 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="utf-8"/>
+ <title>Tree Update Removal Test</title>
+ </head>
+ <body id="body">
+ <div id="the_displaynone" style="display: none;"></div>
+ <table id="the_table"></table>
+ <tr id="the_row"></tr>
+ </body>
+</html>
diff --git a/accessible/tests/browser/e10s/doc_treeupdate_visibility.html b/accessible/tests/browser/e10s/doc_treeupdate_visibility.html
new file mode 100644
index 000000000..c33a2bc02
--- /dev/null
+++ b/accessible/tests/browser/e10s/doc_treeupdate_visibility.html
@@ -0,0 +1,78 @@
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Tree Update Visibility Test</title>
+ </head>
+ <body id="body">
+ <!-- hide parent while child stays visible -->
+ <div id="t1_container">
+ <div id="t1_parent">
+ <div id="t1_child" style="visibility: visible">text</div>
+ </div>
+ </div>
+
+ <!-- hide grandparent while its children stay visible -->
+ <div id="t2_container">
+ <div id="t2_grandparent">
+ <div>
+ <div id="t2_child" style="visibility: visible">text</div>
+ <div id="t2_child2" style="visibility: visible">text</div>
+ </div>
+ </div>
+ </div>
+
+ <!-- change container style, hide parents while their children stay visible -->
+ <div id="t3_container">
+ <div id="t3_parent">
+ <div id="t3_child" style="visibility: visible">text</div>
+ </div>
+ <div id="t3_parent2">
+ <div id="t3_child2" style="visibility: visible">text</div>
+ </div>
+ </div>
+
+ <!-- change container style, show child inside the table -->
+ <div id="t4_container">
+ <table>
+ <tr>
+ <td id="t4_parent">
+ <div id="t4_child" style="visibility: hidden;">text</div>
+ </td>
+ </tr>
+ </table>
+ </div>
+
+ <!-- hide subcontainer while child inside the table stays visible -->
+ <div id="t5_container">
+ <div id="t5_subcontainer">
+ <table>
+ <tr>
+ <td>
+ <div id="t5_child" style="visibility: visible;">text</div>
+ </td>
+ </tr>
+ </table>
+ </div>
+ </div>
+
+ <!-- hide subcontainer while its child and child inside the nested table stays visible -->
+ <div id="t6_container">
+ <div id="t6_subcontainer">
+ <table>
+ <tr>
+ <td>
+ <table>
+ <tr>
+ <td>
+ <div id="t6_child" style="visibility: visible;">text</div>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ <div id="t6_child2" style="visibility: visible">text</div>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/accessible/tests/browser/e10s/doc_treeupdate_whitespace.html b/accessible/tests/browser/e10s/doc_treeupdate_whitespace.html
new file mode 100644
index 000000000..d1770d300
--- /dev/null
+++ b/accessible/tests/browser/e10s/doc_treeupdate_whitespace.html
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="utf-8"/>
+ <title>Whitespace text accessible creation/desctruction</title>
+ </head>
+ <body id="body">
+ <div id="container1"> <img src="http://example.com/a11y/accessible/tests/mochitest/moz.png"> <img id="img1" src="http://example.com/a11y/accessible/tests/mochitest/moz.png"> <img src="http://example.com/a11y/accessible/tests/mochitest/moz.png"> </div>
+ <div id="container2-parent"> <a id="container2"></a> <a><img src="http://example.com/a11y/accessible/tests/mochitest/moz.png"></a> </div>
+ </body>
+</html>
diff --git a/accessible/tests/browser/e10s/events.js b/accessible/tests/browser/e10s/events.js
new file mode 100644
index 000000000..39dd743ef
--- /dev/null
+++ b/accessible/tests/browser/e10s/events.js
@@ -0,0 +1,127 @@
+/* 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/. */
+
+'use strict';
+
+/* global nsIAccessibleEvent, nsIAccessibleDocument,
+ nsIAccessibleStateChangeEvent, nsIAccessibleTextChangeEvent */
+
+/* exported EVENT_REORDER, EVENT_SHOW, EVENT_TEXT_INSERTED, EVENT_TEXT_REMOVED,
+ EVENT_DOCUMENT_LOAD_COMPLETE, EVENT_HIDE, EVENT_TEXT_CARET_MOVED,
+ EVENT_DESCRIPTION_CHANGE, EVENT_NAME_CHANGE, EVENT_STATE_CHANGE,
+ EVENT_VALUE_CHANGE, EVENT_TEXT_VALUE_CHANGE, EVENT_FOCUS,
+ waitForEvent, waitForMultipleEvents */
+
+const EVENT_DOCUMENT_LOAD_COMPLETE = nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE;
+const EVENT_HIDE = nsIAccessibleEvent.EVENT_HIDE;
+const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER;
+const EVENT_SHOW = nsIAccessibleEvent.EVENT_SHOW;
+const EVENT_STATE_CHANGE = nsIAccessibleEvent.EVENT_STATE_CHANGE;
+const EVENT_TEXT_CARET_MOVED = nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED;
+const EVENT_TEXT_INSERTED = nsIAccessibleEvent.EVENT_TEXT_INSERTED;
+const EVENT_TEXT_REMOVED = nsIAccessibleEvent.EVENT_TEXT_REMOVED;
+const EVENT_DESCRIPTION_CHANGE = nsIAccessibleEvent.EVENT_DESCRIPTION_CHANGE;
+const EVENT_NAME_CHANGE = nsIAccessibleEvent.EVENT_NAME_CHANGE;
+const EVENT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_VALUE_CHANGE;
+const EVENT_TEXT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_TEXT_VALUE_CHANGE;
+const EVENT_FOCUS = nsIAccessibleEvent.EVENT_FOCUS;
+
+/**
+ * Describe an event in string format.
+ * @param {nsIAccessibleEvent} event event to strigify
+ */
+function eventToString(event) {
+ let type = eventTypeToString(event.eventType);
+ let info = `Event type: ${type}`;
+
+ if (event instanceof nsIAccessibleStateChangeEvent) {
+ let stateStr = statesToString(event.isExtraState ? 0 : event.state,
+ event.isExtraState ? event.state : 0);
+ info += `, state: ${stateStr}, is enabled: ${event.isEnabled}`;
+ } else if (event instanceof nsIAccessibleTextChangeEvent) {
+ let tcType = event.isInserted ? 'inserted' : 'removed';
+ info += `, start: ${event.start}, length: ${event.length}, ${tcType} text: ${event.modifiedText}`;
+ }
+
+ info += `. Target: ${prettyName(event.accessible)}`;
+ return info;
+}
+
+/**
+ * A helper function that returns a promise that resolves when an accessible
+ * event of the given type with the given target (defined by its id or
+ * accessible) is observed.
+ * @param {String|nsIAccessible} expectedIdOrAcc expected content element id
+ * for the event
+ * @param {Number} eventType expected accessible event
+ * type
+ * @return {Promise} promise that resolves to an
+ * event
+ */
+function waitForEvent(eventType, expectedIdOrAcc) {
+ return new Promise(resolve => {
+ let eventObserver = {
+ observe(subject, topic, data) {
+ if (topic !== 'accessible-event') {
+ return;
+ }
+
+ let event = subject.QueryInterface(nsIAccessibleEvent);
+ if (Logger.enabled) {
+ // Avoid calling eventToString if the logger isn't enabled in order
+ // to avoid an intermittent crash (bug 1307645).
+ Logger.log(eventToString(event));
+ }
+
+ // If event type does not match expected type, skip the event.
+ if (event.eventType !== eventType) {
+ return;
+ }
+
+ let acc = event.accessible;
+ let id = getAccessibleDOMNodeID(acc);
+ let isID = typeof expectedIdOrAcc === 'string';
+ let actualIdOrAcc = isID ? id : acc;
+ // If event's accessible does not match expected DOMNode id or
+ // accessible, skip the event.
+ if (actualIdOrAcc === expectedIdOrAcc) {
+ if (isID) {
+ Logger.log(`Correct event DOMNode id: ${id}`);
+ } else {
+ Logger.log(`Correct event accessible: ${prettyName(acc)}`);
+ }
+ Logger.log(`Correct event type: ${eventTypeToString(eventType)}`);
+ ok(event.accessibleDocument instanceof nsIAccessibleDocument,
+ 'Accessible document present.');
+
+ Services.obs.removeObserver(this, 'accessible-event');
+ resolve(event);
+ }
+ }
+ };
+ Services.obs.addObserver(eventObserver, 'accessible-event', false);
+ });
+}
+
+/**
+ * A helper function that waits for a sequence of accessible events in
+ * specified order.
+ * @param {Array} events a list of events to wait (same format as
+ * waitForEvent arguments)
+ */
+function waitForMultipleEvents(events) {
+ // Next expected event index.
+ let currentIdx = 0;
+
+ return Promise.all(events.map(({ eventType, id }, idx) =>
+ // In addition to waiting for an event, attach an order checker for the
+ // event.
+ waitForEvent(eventType, id).then(resolvedEvent => {
+ // Verify that event happens in order and increment expected index.
+ is(idx, currentIdx++,
+ `Unexpected event order: ${eventToString(resolvedEvent)}`);
+ return resolvedEvent;
+ })
+ ));
+}
diff --git a/accessible/tests/browser/e10s/head.js b/accessible/tests/browser/e10s/head.js
new file mode 100644
index 000000000..5cc102697
--- /dev/null
+++ b/accessible/tests/browser/e10s/head.js
@@ -0,0 +1,84 @@
+/* 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/. */
+
+'use strict';
+
+/* global EVENT_DOCUMENT_LOAD_COMPLETE, CURRENT_CONTENT_DIR, loadFrameScripts */
+
+/* exported addAccessibleTask */
+
+// Load the shared-head file first.
+Services.scriptloader.loadSubScript(
+ 'chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js',
+ this);
+
+/**
+ * A wrapper around browser test add_task that triggers an accessible test task
+ * as a new browser test task with given document, data URL or markup snippet.
+ * @param {String} doc URL (relative to current directory) or
+ * data URL or markup snippet that is used
+ * to test content with
+ * @param {Function|Function*} task a generator or a function with tests to
+ * run
+ */
+function addAccessibleTask(doc, task) {
+ add_task(function*() {
+ let url;
+ if (doc.includes('doc_')) {
+ url = `${CURRENT_CONTENT_DIR}e10s/${doc}`;
+ } else {
+ // Assume it's a markup snippet.
+ url = `data:text/html,
+ <html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Accessibility Test</title>
+ </head>
+ <body id="body">${doc}</body>
+ </html>`;
+ }
+
+ registerCleanupFunction(() => {
+ let observers = Services.obs.enumerateObservers('accessible-event');
+ while (observers.hasMoreElements()) {
+ Services.obs.removeObserver(
+ observers.getNext().QueryInterface(Ci.nsIObserver),
+ 'accessible-event');
+ }
+ });
+
+ let onDocLoad = waitForEvent(EVENT_DOCUMENT_LOAD_COMPLETE, 'body');
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: url
+ }, function*(browser) {
+ registerCleanupFunction(() => {
+ if (browser) {
+ let tab = gBrowser.getTabForBrowser(browser);
+ if (tab && !tab.closing && tab.linkedBrowser) {
+ gBrowser.removeTab(tab);
+ }
+ }
+ });
+
+ yield SimpleTest.promiseFocus(browser);
+
+ loadFrameScripts(browser,
+ 'let { document, window, navigator } = content;',
+ { name: 'common.js', dir: MOCHITESTS_DIR });
+
+ Logger.log(
+ `e10s enabled: ${Services.appinfo.browserTabsRemoteAutostart}`);
+ Logger.log(`Actually remote browser: ${browser.isRemoteBrowser}`);
+
+ let event = yield onDocLoad;
+ yield task(browser, event.accessible);
+ });
+ });
+}
+
+// Loading and common.js from accessible/tests/mochitest/ for all tests, as
+// well as events.js.
+loadScripts({ name: 'common.js', dir: MOCHITESTS_DIR }, 'e10s/events.js');
diff --git a/accessible/tests/browser/head.js b/accessible/tests/browser/head.js
new file mode 100644
index 000000000..8e8d82205
--- /dev/null
+++ b/accessible/tests/browser/head.js
@@ -0,0 +1,116 @@
+/* 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/. */
+
+'use strict';
+
+/* exported initPromise, shutdownPromise,
+ setE10sPrefs, unsetE10sPrefs, forceGC */
+
+/**
+ * Set e10s related preferences in the test environment.
+ * @return {Promise} promise that resolves when preferences are set.
+ */
+function setE10sPrefs() {
+ return new Promise(resolve =>
+ SpecialPowers.pushPrefEnv({
+ set: [
+ ['browser.tabs.remote.autostart', true],
+ ['browser.tabs.remote.force-enable', true],
+ ['extensions.e10sBlocksEnabling', false]
+ ]
+ }, resolve));
+}
+
+/**
+ * Unset e10s related preferences in the test environment.
+ * @return {Promise} promise that resolves when preferences are unset.
+ */
+function unsetE10sPrefs() {
+ return new Promise(resolve => {
+ SpecialPowers.popPrefEnv(resolve);
+ });
+}
+
+// Load the shared-head file first.
+Services.scriptloader.loadSubScript(
+ 'chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js',
+ this);
+
+/**
+ * Returns a promise that resolves when 'a11y-init-or-shutdown' event is fired.
+ * @return {Promise} event promise evaluating to event's data
+ */
+function a11yInitOrShutdownPromise() {
+ return new Promise(resolve => {
+ let observe = (subject, topic, data) => {
+ Services.obs.removeObserver(observe, 'a11y-init-or-shutdown');
+ resolve(data);
+ };
+ Services.obs.addObserver(observe, 'a11y-init-or-shutdown', false);
+ });
+}
+
+/**
+ * Returns a promise that resolves when 'a11y-init-or-shutdown' event is fired
+ * in content.
+ * @param {Object} browser current "tabbrowser" element
+ * @return {Promise} event promise evaluating to event's data
+ */
+function contentA11yInitOrShutdownPromise(browser) {
+ return ContentTask.spawn(browser, {}, a11yInitOrShutdownPromise);
+}
+
+/**
+ * A helper function that maps 'a11y-init-or-shutdown' event to a promise that
+ * resovles or rejects depending on whether accessibility service is expected to
+ * be initialized or shut down.
+ */
+function promiseOK(promise, expected) {
+ return promise.then(flag =>
+ flag === expected ? Promise.resolve() : Promise.reject());
+}
+
+/**
+ * Checks and returns a promise that resolves when accessibility service is
+ * initialized with the correct flag.
+ * @param {?Object} contentBrowser optinal remove browser object that indicates
+ * that accessibility service is expected to be
+ * initialized in content process.
+ * @return {Promise} promise that resolves when the accessibility
+ * service initialized correctly.
+ */
+function initPromise(contentBrowser) {
+ let a11yInitPromise = contentBrowser ?
+ contentA11yInitOrShutdownPromise(contentBrowser) :
+ a11yInitOrShutdownPromise();
+ return promiseOK(a11yInitPromise, '1').then(
+ () => ok(true, 'Service initialized correctly'),
+ () => ok(false, 'Service shutdown incorrectly'));
+}
+
+/**
+ * Checks and returns a promise that resolves when accessibility service is
+ * shut down with the correct flag.
+ * @param {?Object} contentBrowser optinal remove browser object that indicates
+ * that accessibility service is expected to be
+ * shut down in content process.
+ * @return {Promise} promise that resolves when the accessibility
+ * service shuts down correctly.
+ */
+function shutdownPromise(contentBrowser) {
+ let a11yShutdownPromise = contentBrowser ?
+ contentA11yInitOrShutdownPromise(contentBrowser) :
+ a11yInitOrShutdownPromise();
+ return promiseOK(a11yShutdownPromise, '0').then(
+ () => ok(true, 'Service shutdown correctly'),
+ () => ok(false, 'Service initialized incorrectly'));
+}
+
+/**
+ * Force garbage collection.
+ */
+function forceGC() {
+ Cu.forceCC();
+ Cu.forceGC();
+}
diff --git a/accessible/tests/browser/shared-head.js b/accessible/tests/browser/shared-head.js
new file mode 100644
index 000000000..83a9fa612
--- /dev/null
+++ b/accessible/tests/browser/shared-head.js
@@ -0,0 +1,229 @@
+/* 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/. */
+
+'use strict';
+
+/* exported Logger, MOCHITESTS_DIR, isDefunct, invokeSetAttribute, invokeFocus,
+ invokeSetStyle, findAccessibleChildByID, getAccessibleDOMNodeID,
+ CURRENT_CONTENT_DIR, loadScripts, loadFrameScripts, Cc, Cu */
+
+const { interfaces: Ci, utils: Cu, classes: Cc } = Components;
+
+/**
+ * Current browser test directory path used to load subscripts.
+ */
+const CURRENT_DIR =
+ 'chrome://mochitests/content/browser/accessible/tests/browser/';
+/**
+ * A11y mochitest directory where we find common files used in both browser and
+ * plain tests.
+ */
+const MOCHITESTS_DIR =
+ 'chrome://mochitests/content/a11y/accessible/tests/mochitest/';
+/**
+ * A base URL for test files used in content.
+ */
+const CURRENT_CONTENT_DIR =
+ 'http://example.com/browser/accessible/tests/browser/';
+
+/**
+ * Used to dump debug information.
+ */
+let Logger = {
+ /**
+ * Set up this variable to dump log messages into console.
+ */
+ dumpToConsole: false,
+
+ /**
+ * Set up this variable to dump log messages into error console.
+ */
+ dumpToAppConsole: false,
+
+ /**
+ * Return true if dump is enabled.
+ */
+ get enabled() {
+ return this.dumpToConsole || this.dumpToAppConsole;
+ },
+
+ /**
+ * Dump information into console if applicable.
+ */
+ log(msg) {
+ if (this.enabled) {
+ this.logToConsole(msg);
+ this.logToAppConsole(msg);
+ }
+ },
+
+ /**
+ * Log message to console.
+ */
+ logToConsole(msg) {
+ if (this.dumpToConsole) {
+ dump(`\n${msg}\n`);
+ }
+ },
+
+ /**
+ * Log message to error console.
+ */
+ logToAppConsole(msg) {
+ if (this.dumpToAppConsole) {
+ Services.console.logStringMessage(`${msg}`);
+ }
+ }
+};
+
+/**
+ * Check if an accessible object has a defunct test.
+ * @param {nsIAccessible} accessible object to test defunct state for
+ * @return {Boolean} flag indicating defunct state
+ */
+function isDefunct(accessible) {
+ let defunct = false;
+ try {
+ let extState = {};
+ accessible.getState({}, extState);
+ defunct = extState.value & Ci.nsIAccessibleStates.EXT_STATE_DEFUNCT;
+ } catch (x) {
+ defunct = true;
+ } finally {
+ if (defunct) {
+ Logger.log(`Defunct accessible: ${prettyName(accessible)}`);
+ }
+ }
+ return defunct;
+}
+
+/**
+ * Asynchronously set or remove content element's attribute (in content process
+ * if e10s is enabled).
+ * @param {Object} browser current "tabbrowser" element
+ * @param {String} id content element id
+ * @param {String} attr attribute name
+ * @param {String?} value optional attribute value, if not present, remove
+ * attribute
+ * @return {Promise} promise indicating that attribute is set/removed
+ */
+function invokeSetAttribute(browser, id, attr, value) {
+ if (value) {
+ Logger.log(`Setting ${attr} attribute to ${value} for node with id: ${id}`);
+ } else {
+ Logger.log(`Removing ${attr} attribute from node with id: ${id}`);
+ }
+ return ContentTask.spawn(browser, [id, attr, value],
+ ([contentId, contentAttr, contentValue]) => {
+ let elm = content.document.getElementById(contentId);
+ if (contentValue) {
+ elm.setAttribute(contentAttr, contentValue);
+ } else {
+ elm.removeAttribute(contentAttr);
+ }
+ });
+}
+
+/**
+ * Asynchronously set or remove content element's style (in content process if
+ * e10s is enabled).
+ * @param {Object} browser current "tabbrowser" element
+ * @param {String} id content element id
+ * @param {String} aStyle style property name
+ * @param {String?} aValue optional style property value, if not present,
+ * remove style
+ * @return {Promise} promise indicating that style is set/removed
+ */
+function invokeSetStyle(browser, id, style, value) {
+ if (value) {
+ Logger.log(`Setting ${style} style to ${value} for node with id: ${id}`);
+ } else {
+ Logger.log(`Removing ${style} style from node with id: ${id}`);
+ }
+ return ContentTask.spawn(browser, [id, style, value],
+ ([contentId, contentStyle, contentValue]) => {
+ let elm = content.document.getElementById(contentId);
+ if (contentValue) {
+ elm.style[contentStyle] = contentValue;
+ } else {
+ delete elm.style[contentStyle];
+ }
+ });
+}
+
+/**
+ * Asynchronously set focus on a content element (in content process if e10s is
+ * enabled).
+ * @param {Object} browser current "tabbrowser" element
+ * @param {String} id content element id
+ * @return {Promise} promise indicating that focus is set
+ */
+function invokeFocus(browser, id) {
+ Logger.log(`Setting focus on a node with id: ${id}`);
+ return ContentTask.spawn(browser, id, contentId => {
+ let elm = content.document.getElementById(contentId);
+ if (elm instanceof Ci.nsIDOMNSEditableElement && elm.editor ||
+ elm instanceof Ci.nsIDOMXULTextBoxElement) {
+ elm.selectionStart = elm.selectionEnd = elm.value.length;
+ }
+ elm.focus();
+ });
+}
+
+/**
+ * Traverses the accessible tree starting from a given accessible as a root and
+ * looks for an accessible that matches based on its DOMNode id.
+ * @param {nsIAccessible} accessible root accessible
+ * @param {String} id id to look up accessible for
+ * @return {nsIAccessible?} found accessible if any
+ */
+function findAccessibleChildByID(accessible, id) {
+ if (getAccessibleDOMNodeID(accessible) === id) {
+ return accessible;
+ }
+ for (let i = 0; i < accessible.children.length; ++i) {
+ let found = findAccessibleChildByID(accessible.getChildAt(i), id);
+ if (found) {
+ return found;
+ }
+ }
+}
+
+/**
+ * Load a list of scripts into the test
+ * @param {Array} scripts a list of scripts to load
+ */
+function loadScripts(...scripts) {
+ for (let script of scripts) {
+ let path = typeof script === 'string' ? `${CURRENT_DIR}${script}` :
+ `${script.dir}${script.name}`;
+ Services.scriptloader.loadSubScript(path, this);
+ }
+}
+
+/**
+ * Load a list of frame scripts into test's content.
+ * @param {Object} browser browser element that content belongs to
+ * @param {Array} scripts a list of scripts to load into content
+ */
+function loadFrameScripts(browser, ...scripts) {
+ let mm = browser.messageManager;
+ for (let script of scripts) {
+ let frameScript;
+ if (typeof script === 'string') {
+ if (script.includes('.js')) {
+ // If script string includes a .js extention, assume it is a script
+ // path.
+ frameScript = `${CURRENT_DIR}${script}`;
+ } else {
+ // Otherwise it is a serealized script.
+ frameScript = `data:,${script}`;
+ }
+ } else {
+ // Script is a object that has { dir, name } format.
+ frameScript = `${script.dir}${script.name}`;
+ }
+ mm.loadFrameScript(frameScript, false, true);
+ }
+}
diff --git a/accessible/tests/crashtests/448064.xhtml b/accessible/tests/crashtests/448064.xhtml
new file mode 100644
index 000000000..64d6d851d
--- /dev/null
+++ b/accessible/tests/crashtests/448064.xhtml
@@ -0,0 +1,73 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+<body>
+<div id="mw_b">
+<div id="mw_f">
+<div id="mw_g" style="display: none;"/>
+</div>
+</div>
+
+<div id="mw_c" style="display: none;">
+<div id="mw_d">
+<div id="mw_e"></div>
+</div>
+</div>
+
+<input id="mw_a"/>
+
+
+<script>
+function dumpAccessibleNode(aNode, level) {
+ var msg = "";
+
+ try {
+ msg += "name=\"" + aNode.name + "\" ";
+ } catch (e) {
+ msg += " noName ";
+ }
+
+ dump(msg + '\n');
+}
+
+
+function dumpAccessibleTree(aNode, level) {
+netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+ level = level || 0;
+
+ dumpAccessibleNode(aNode, level);
+ try {
+ var child = aNode.firstChild;
+ while (child) {
+ dumpAccessibleTree(child, level + 1);
+ child = child.nextSibling;
+ }
+ } catch (e) {
+ dump("Error visiting child nodes: " + e + '\n');
+ }
+}
+
+function A(o) {
+ netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+ var acc = Components.classes['@mozilla.org/accessibilityService;1']
+ .getService(Components.interfaces.nsIAccessibilityService);
+ return acc.getAccessibleFor(o);
+}
+
+function beginAccessible() {
+ netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+ dumpAccessibleTree(A(document),0);
+}
+setTimeout(beginAccessible, 100);
+
+
+setTimeout(doe, 200);
+function doe() {
+ document.getElementById('mw_a').appendChild(document.getElementById('mw_b'));
+ document.getElementById('mw_c').appendChild(document.getElementById('mw_d'));
+ document.getElementById('mw_e').appendChild(document.getElementById('mw_f'));
+ document.getElementById('mw_g').appendChild(document.getElementById('mw_b'));
+}
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/accessible/tests/crashtests/471493.xul b/accessible/tests/crashtests/471493.xul
new file mode 100644
index 000000000..2524d47cc
--- /dev/null
+++ b/accessible/tests/crashtests/471493.xul
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="bug 471493 'crash [@ nsPropertyTable::GetPropertyInternal]'"
+ onload="doTest();">
+
+ <script type="application/javascript">
+ <![CDATA[
+ function doTest()
+ {
+ var accService = SpecialPowers.Cc["@mozilla.org/accessibilityService;1"].
+ getService(SpecialPowers.Ci.nsIAccessibilityService);
+
+ var treecol = document.getElementById("col");
+ var x = treecol.boxObject.screenX;
+ var y = treecol.boxObject.screenY;
+
+ var tree = document.getElementById("tree");
+ var treeAcc = accService.getAccessibleFor(tree);
+ treeAcc.getChildAtPoint(x + 1, y + 1);
+ }
+ ]]>
+ </script>
+
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ <treecol id="scol" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren id="treechildren"/>
+ </tree>
+
+</window>
+
diff --git a/accessible/tests/crashtests/crashtests.list b/accessible/tests/crashtests/crashtests.list
new file mode 100644
index 000000000..6718706aa
--- /dev/null
+++ b/accessible/tests/crashtests/crashtests.list
@@ -0,0 +1,3 @@
+# Disabled because they cause assertions/crashes in later crashtests, see bug 918246
+skip load 448064.xhtml
+skip load 471493.xul
diff --git a/accessible/tests/mochitest/a11y.ini b/accessible/tests/mochitest/a11y.ini
new file mode 100644
index 000000000..b197c7007
--- /dev/null
+++ b/accessible/tests/mochitest/a11y.ini
@@ -0,0 +1,17 @@
+[DEFAULT]
+support-files =
+ ../../../dom/media/test/bug461281.ogg
+ dumbfile.zip
+ formimage.png
+ letters.gif
+ moz.png
+ longdesc_src.html
+ *.js
+ treeview.css
+
+[test_aria_token_attrs.html]
+[test_bug420863.html]
+[test_descr.html]
+[test_nsIAccessibleDocument.html]
+[test_nsIAccessibleImage.html]
+[test_OuterDocAccessible.html]
diff --git a/accessible/tests/mochitest/actions.js b/accessible/tests/mochitest/actions.js
new file mode 100644
index 000000000..0c2765eeb
--- /dev/null
+++ b/accessible/tests/mochitest/actions.js
@@ -0,0 +1,187 @@
+////////////////////////////////////////////////////////////////////////////////
+// Event constants
+
+const MOUSEDOWN_EVENT = 1;
+const MOUSEUP_EVENT = 2;
+const CLICK_EVENT = 4;
+const COMMAND_EVENT = 8;
+const FOCUS_EVENT = 16;
+
+const CLICK_EVENTS = MOUSEDOWN_EVENT | MOUSEUP_EVENT | CLICK_EVENT;
+const XUL_EVENTS = CLICK_EVENTS | COMMAND_EVENT;
+
+////////////////////////////////////////////////////////////////////////////////
+// Public functions
+
+/**
+ * Test default accessible actions.
+ *
+ * Action tester interface is:
+ *
+ * var actionObj = {
+ * // identifier of accessible to perform an action on
+ * get ID() {},
+ *
+ * // index of the action
+ * get actionIndex() {},
+ *
+ * // name of the action
+ * get actionName() {},
+ *
+ * // DOM events (see constants defined above)
+ * get events() {},
+ *
+ * // [optional] identifier of target DOM events listeners are registered on,
+ * // used with 'events', if missing then 'ID' is used instead.
+ * get targetID() {},
+ *
+ * // [optional] perform checks when 'click' event is handled if 'events'
+ * // is used.
+ * checkOnClickEvent: function() {},
+ *
+ * // [optional] an array of invoker's checker objects (see eventQueue
+ * // constructor events.js)
+ * get eventSeq() {}
+ * };
+ *
+ *
+ * @param aArray [in] an array of action cheker objects
+ */
+function testActions(aArray)
+{
+ gActionsQueue = new eventQueue();
+
+ for (var idx = 0; idx < aArray.length; idx++) {
+
+ var actionObj = aArray[idx];
+ var accOrElmOrID = actionObj.ID;
+ var actionIndex = actionObj.actionIndex;
+ var actionName = actionObj.actionName;
+ var events = actionObj.events;
+ var accOrElmOrIDOfTarget = actionObj.targetID ?
+ actionObj.targetID : accOrElmOrID;
+
+ var eventSeq = new Array();
+ if (events) {
+ var elm = getNode(accOrElmOrIDOfTarget);
+ if (events & MOUSEDOWN_EVENT)
+ eventSeq.push(new checkerOfActionInvoker("mousedown", elm));
+
+ if (events & MOUSEUP_EVENT)
+ eventSeq.push(new checkerOfActionInvoker("mouseup", elm));
+
+ if (events & CLICK_EVENT)
+ eventSeq.push(new checkerOfActionInvoker("click", elm, actionObj));
+
+ if (events & COMMAND_EVENT)
+ eventSeq.push(new checkerOfActionInvoker("command", elm));
+
+ if (events & FOCUS_EVENT)
+ eventSeq.push(new focusChecker(elm));
+ }
+
+ if (actionObj.eventSeq)
+ eventSeq = eventSeq.concat(actionObj.eventSeq);
+
+ var invoker = new actionInvoker(accOrElmOrID, actionIndex, actionName,
+ eventSeq);
+ gActionsQueue.push(invoker);
+ }
+
+ gActionsQueue.invoke();
+}
+
+/**
+ * Test action names and descriptions.
+ */
+function testActionNames(aID, aActions)
+{
+ var actions = (typeof aActions == "string") ?
+ [ aActions ] : (aActions || []);
+
+ var acc = getAccessible(aID);
+ is(acc.actionCount, actions.length, "Wong number of actions.");
+ for (var i = 0; i < actions.length; i++ ) {
+ is(acc.getActionName(i), actions[i], "Wrong action name at " + i + " index.");
+ is(acc.getActionDescription(0), gActionDescrMap[actions[i]],
+ "Wrong action description at " + i + "index.");
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Private
+
+var gActionsQueue = null;
+
+function actionInvoker(aAccOrElmOrId, aActionIndex, aActionName, aEventSeq)
+{
+ this.invoke = function actionInvoker_invoke()
+ {
+ var acc = getAccessible(aAccOrElmOrId);
+ if (!acc)
+ return INVOKER_ACTION_FAILED;
+
+ var isThereActions = acc.actionCount > 0;
+ ok(isThereActions,
+ "No actions on the accessible for " + prettyName(aAccOrElmOrId));
+
+ if (!isThereActions)
+ return INVOKER_ACTION_FAILED;
+
+ is(acc.getActionName(aActionIndex), aActionName,
+ "Wrong action name of the accessible for " + prettyName(aAccOrElmOrId));
+
+ try {
+ acc.doAction(aActionIndex);
+ }
+ catch (e){
+ ok(false, "doAction(" + aActionIndex + ") failed with: " + e.name);
+ return INVOKER_ACTION_FAILED;
+ }
+ }
+
+ this.eventSeq = aEventSeq;
+
+ this.getID = function actionInvoker_getID()
+ {
+ return "invoke an action " + aActionName + " at index " + aActionIndex +
+ " on " + prettyName(aAccOrElmOrId);
+ }
+}
+
+function checkerOfActionInvoker(aType, aTarget, aActionObj)
+{
+ this.type = aType;
+
+ this.target = aTarget;
+
+ this.phase = false;
+
+ this.getID = function getID()
+ {
+ return aType + " event handling";
+ }
+
+ this.check = function check(aEvent)
+ {
+ if (aActionObj && "checkOnClickEvent" in aActionObj)
+ aActionObj.checkOnClickEvent(aEvent);
+ }
+}
+
+var gActionDescrMap =
+{
+ jump: "Jump",
+ press: "Press",
+ check: "Check",
+ uncheck: "Uncheck",
+ select: "Select",
+ open: "Open",
+ close: "Close",
+ switch: "Switch",
+ click: "Click",
+ collapse: "Collapse",
+ expand: "Expand",
+ activate: "Activate",
+ cycle: "Cycle"
+};
diff --git a/accessible/tests/mochitest/actions/a11y.ini b/accessible/tests/mochitest/actions/a11y.ini
new file mode 100644
index 000000000..ca01f4104
--- /dev/null
+++ b/accessible/tests/mochitest/actions/a11y.ini
@@ -0,0 +1,18 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+ !/dom/media/test/bug461281.ogg
+
+[test_anchors.html]
+[test_aria.html]
+[test_controls.html]
+[test_general.html]
+[test_general.xul]
+[test_keys.html]
+[test_keys_menu.xul]
+[test_link.html]
+[test_media.html]
+skip-if = buildapp == 'mulet'
+[test_select.html]
+[test_tree.xul]
+[test_treegrid.xul]
diff --git a/accessible/tests/mochitest/actions/test_anchors.html b/accessible/tests/mochitest/actions/test_anchors.html
new file mode 100644
index 000000000..5e8330683
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_anchors.html
@@ -0,0 +1,150 @@
+<html>
+
+<head>
+ <title>nsIAccessible actions testing for HTML links that
+ scroll the page to named anchors</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../actions.js"></script>
+
+ <script type="application/javascript">
+ ////////////////////////////////////////////////////////////////////////////
+ // Event checkers
+
+ function scrollingChecker(aAcc)
+ {
+ this.type = EVENT_SCROLLING_START;
+ this.target = aAcc;
+ this.getID = function scrollingChecker_getID()
+ {
+ return "scrolling start handling for " + prettyName(aAcc);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "debug"; // debug stuff
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ function doTest()
+ {
+ var actionsArray = [
+ {
+ ID: "anchor1",
+ actionName: "jump",
+ actionIndex: 0,
+ events: CLICK_EVENTS,
+ eventSeq: [
+ new scrollingChecker(getAccessible("bottom1"))
+ ]
+ },
+ { // jump again (test for bug 437607)
+ ID: "anchor1",
+ actionName: "jump",
+ actionIndex: 0,
+ events: CLICK_EVENTS,
+ eventSeq: [
+ new scrollingChecker(getAccessible("bottom1"))
+ ]
+ },
+ {
+ ID: "anchor2",
+ actionName: "jump",
+ actionIndex: 0,
+ events: CLICK_EVENTS,
+ eventSeq: [
+ new scrollingChecker(getAccessible("bottom2"))
+ ]
+ }
+ ];
+
+ testActions(actionsArray);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=506389"
+ title="Some same page links do not fire EVENT_SYSTEM_SCROLLINGSTART">
+ Mozilla Bug 506389
+ </a><br>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=437607"
+ title="Clicking the 'Skip to main content' link once works, second time fails to initiate a V cursor jump">
+ Mozilla Bug 437607
+ </a><br>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=519303"
+ title="Same page links to targets with content fires scrolling start accessible event on leaf text node">
+ Mozilla Bug 519303
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="debug"></div>
+
+ <h1>This is a test page for anchors</h1>
+ This is a top anchor<a name="Top">
+ </a><a id="anchor1" href="#bottom1">Link to anchor</a>
+ <a id="anchor2" href="#bottom2">Link to div</a>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br>This is some text in the middle<br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ This is some text.
+ This is a bottom anchor<a id="bottom1"></a>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <div id="bottom2">This is a div</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/actions/test_aria.html b/accessible/tests/mochitest/actions/test_aria.html
new file mode 100644
index 000000000..c4eae812d
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_aria.html
@@ -0,0 +1,202 @@
+<html>
+
+<head>
+ <title>nsIAccessible actions testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../actions.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ var actionsArray = [
+ {
+ ID: "clickable",
+ actionName: "click",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "button",
+ actionName: "press",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "checkbox_unchecked",
+ actionName: "check",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "checkbox_checked",
+ actionName: "uncheck",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "checkbox_mixed",
+ actionName: "cycle",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "combobox_collapsed",
+ actionName: "open",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "combobox_expanded",
+ actionName: "close",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "link",
+ actionName: "jump",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "menuitem",
+ actionName: "click",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "menuitemcheckbox",
+ actionName: "click",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "menuitemradio",
+ actionName: "click",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "option",
+ actionName: "select",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "radio",
+ actionName: "select",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "switch_unchecked",
+ actionName: "check",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "switch_checked",
+ actionName: "uncheck",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "tab",
+ actionName: "switch",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "textbox",
+ actionName: "activate",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "treeitem",
+ actionName: "activate",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "sortable",
+ actionName: "sort",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "expandable",
+ actionName: "expand",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "collapseable",
+ actionName: "collapse",
+ events: CLICK_EVENTS
+ }
+ ];
+ testActions(actionsArray);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=410765"
+ title="nsIAccessible actions testing">
+ Mozilla Bug 410765
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="clickable" onclick="">Clickable text</div>
+
+ <div id="button" role="button">Button</div>
+
+ <div id="checkbox_unchecked" role="checkbox">Checkbox</div>
+
+ <div id="checkbox_checked" role="checkbox" aria-checked="true">Checkbox</div>
+
+ <div id="checkbox_mixed" role="checkbox" aria-checked="mixed">Checkbox</div>
+
+ <div id="combobox_collapsed" role="combobox">
+ <div id="option" role="option">Option of collapsed combobox</div>
+ </div>
+
+ <div id="combobox_expanded" role="combobox" aria-expanded="true">
+ <div role="option">Option of expanded combobox</div>
+ </div>
+
+ <div id="link" role="link">Link</div>
+
+ <div role="menu">
+ <div id="menuitem" role="menuitem">Menuitem</div>
+ <div id="menuitemcheckbox" role="menuitemcheckbox">Menuitem checkbox</div>
+ <div id="menuitemradio" role="menuitemradio">Menuitem radio</div>
+ </div>
+
+ <div role="radiogroup">
+ <div id="radio" role="radio">Radio</div>
+ </div>
+
+ <div id="switch_unchecked" role="switch">Switch</div>
+
+ <div id="switch_checked" role="switch" aria-checked="true">Switch</div>
+
+ <div role="tablist">
+ <div id="tab" role="tab">Tab</div>
+ </div>
+
+ <div id="textbox" role="textbox">Textbox</div>
+
+ <div role="tree">
+ <div id="treeitem" role="treeitem">Treeitem</div>
+ </div>
+
+ <div role="grid">
+ <div id="sortable" role="columnheader" aria-sort="ascending">
+ Columnheader
+ </div>
+ </div>
+
+ <div id="expandable" aria-expanded="false">collapsed</div>
+ <div id="collapseable" aria-expanded="true">expanded</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/actions/test_controls.html b/accessible/tests/mochitest/actions/test_controls.html
new file mode 100644
index 000000000..d6109a82f
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_controls.html
@@ -0,0 +1,109 @@
+<html>
+
+<head>
+ <title>nsIAccessible actions testing for inputs</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../actions.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ var actionsArray = [
+ {
+ ID: "button",
+ actionName: "press",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "input_button",
+ actionName: "press",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "checkbox_unchecked",
+ actionName: "check",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "checkbox_checked",
+ actionName: "uncheck",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "checkbox_mixed",
+ actionName: "cycle",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "radio",
+ actionName: "select",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "textarea",
+ actionName: "activate",
+ events: FOCUS_EVENT
+ },
+ {
+ ID: "textinput",
+ actionName: "activate",
+ events: FOCUS_EVENT
+ }
+
+ ];
+ document.getElementById("checkbox_mixed").indeterminate = true;
+
+ testActions(actionsArray);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=477975"
+ title="nsIAccessible actions testing">
+ Mozilla Bug 477975
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <button id="button">Button</button>
+
+ <input id="input_button" type="button" value="normal">
+
+ <input id="checkbox_unchecked" type="checkbox">Checkbox</input>
+
+ <input id="checkbox_checked" type="checkbox" checked="true">Checkbox</input>
+
+ <input id="checkbox_mixed" type="checkbox">Checkbox</input>
+
+ <fieldset>
+ <input id="radio" type="radio">Radio</input>
+ </fieldset>
+
+ <textarea id="textarea" placeholder="What's happening?"></textarea>
+
+ <input id="textinput" type="text">
+</body>
+</html>
diff --git a/accessible/tests/mochitest/actions/test_general.html b/accessible/tests/mochitest/actions/test_general.html
new file mode 100644
index 000000000..5b9a18dab
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_general.html
@@ -0,0 +1,107 @@
+<html>
+
+<head>
+ <title>nsIAccessible actions testing on HTML elements</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../actions.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ var actionsArray = [
+ {
+ ID: "li_clickable1",
+ actionName: "click",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "li_clickable2",
+ actionName: "click",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "li_clickable3",
+ actionName: "click",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "onclick_img",
+ actionName: "click",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "label1",
+ actionName: "click",
+ events: CLICK_EVENTS
+ }
+
+ ];
+
+ testActions(actionsArray);
+
+ is(getAccessible("label1").firstChild.actionCount, 1, "label text should have 1 action");
+
+ getAccessible("onclick_img").takeFocus();
+ is(getAccessible("link1").actionCount, 1, "links should have one action");
+ is(getAccessible("link2").actionCount, 1, "link with onclick handler should have 1 action");
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=523789"
+ title="nsHTMLLiAccessible shouldn't be inherited from linkable accessible">
+ Mozilla Bug 523789
+ </a><br>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=423409"
+ title="Expose click action if mouseup and mousedown are registered">
+ Mozilla Bug 423409
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=659620"
+ title="hang when trying to edit a page on wikimo with NVDA running">
+ Mozilla Bug 659620
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <ul>
+ <li id="li_clickable1" onclick="">Clickable list item</li>
+ <li id="li_clickable2" onmousedown="">Clickable list item</li>
+ <li id="li_clickable3" onmouseup="">Clickable list item</li>
+ </ul>
+
+ <!-- linkable accessibles -->
+ <img id="onclick_img" onclick="" src="../moz.png">
+
+ <a id="link1" href="www">linkable textleaf accessible</a>
+ <div id="link2" onclick="">linkable textleaf accessible</div>
+
+ <div>
+ <label for="TextBox_t2" id="label1">
+ <span>Explicit</span>
+ </label>
+ <input name="in2" id="TextBox_t2" type="text" maxlength="17">
+ </div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/actions/test_general.xul b/accessible/tests/mochitest/actions/test_general.xul
new file mode 100644
index 000000000..14d8bb0d0
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_general.xul
@@ -0,0 +1,145 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<?xml-stylesheet href="../nsIAccessible_name.css"
+ type="text/css"?>
+
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="nsIAccessible actions testing">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../actions.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ //gA11yEventDumpToConsole = true;
+ //enableLogging("tree,verbose"); // debug
+
+ if (navigator.platform.startsWith("Mac")) {
+ SimpleTest.expectAssertions(0, 1);
+ } else {
+ SimpleTest.expectAssertions(0, 1);
+ }
+
+ function doTest()
+ {
+ var actionsArray = [
+ {
+ ID: "menu",
+ actionName: "click",
+ events: CLICK_EVENTS,
+ // Wait for focus event, it guarantees us the submenu tree is created,
+ // that's necessary for next test.
+ eventSeq: [
+ new invokerChecker(EVENT_FOCUS, getNode("menu"))
+ ]
+ },
+ {
+ ID: "submenu",
+ actionName: "click",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "menuitem",
+ actionName: "click",
+ events: XUL_EVENTS
+ },
+ {
+ ID: "button",
+ actionName: "press",
+ events: XUL_EVENTS
+ },
+ {
+ ID: "buttonmenu",
+ actionName: "press",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "name_entry_label",
+ actionName: "click",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "labelWithPopup",
+ actionName: "click",
+ events: CLICK_EVENTS
+ }/*, // XXX: bug 490288
+ {
+ ID: "buttonmenu_item",
+ actionName: "click",
+ events: CLICK_EVENTS
+ }*/
+ ];
+
+ is(getAccessible("name_entry_label").firstChild.actionCount, 1, "label text should have 1 action");
+
+ testActions(actionsArray);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=410765"
+ title="nsIAccessible actions testing">
+ Mozilla Bug 410765
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=504252"
+ title="Expose STATE_HASPOPUP on XUL elements that have an @popup attribute">
+ Mozilla Bug 504252
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <menubar>
+ <menu label="menu" id="menu">
+ <menupopup>
+ <menuitem label="menu item" id="menuitem"/>
+ <menu label="submenu" id="submenu">
+ <menupopup>
+ <menuitem label="menu item"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+ </menu>
+ </menubar>
+
+ <button label="button" id="button"/>
+
+ <button type="menu" id="buttonmenu" label="button">
+ <menupopup>
+ <menuitem label="item1" id="buttonmenu_item"/>
+ <menuitem label="item1"/>
+ </menupopup>
+ </button>
+
+ <label id="labelWithPopup" value="file name"
+ popup="fileContext"
+ tabindex="0"/>
+ <hbox>
+ <label id="name_entry_label" value="Name" control="name_entry"/>
+ <textbox id="name_entry"/>
+ </hbox>
+ </vbox>
+ </hbox>
+</window>
+
diff --git a/accessible/tests/mochitest/actions/test_keys.html b/accessible/tests/mochitest/actions/test_keys.html
new file mode 100644
index 000000000..2feebcadb
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_keys.html
@@ -0,0 +1,60 @@
+<html>
+
+<head>
+ <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+ <title>Keyboard shortcuts tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+
+ <script type="application/javascript">
+ function testAcessKey(aAccOrElmOrID, aKey)
+ {
+ var acc = getAccessible(aAccOrElmOrID);
+ if (!acc)
+ return;
+
+ is(acc.accessKey, aKey,
+ "Wrong keyboard shortcut on " + prettyName(aAccOrElmOrID));
+ }
+
+ function doTest()
+ {
+ testAcessKey("input1", "");
+ testAcessKey("input2", MAC ? "⌃⌥b" : "Alt+Shift+b");
+ testAcessKey("link", MAC ? "⌃⌥l" : "Alt+Shift+l");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=381599"
+ title="Inverse relations cache">
+ Mozilla Bug 381599
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <label accesskey="a">
+ <input id="input1"/>
+ </label>
+ <label accesskey="b" for="input2">
+ <input id="input2"/>
+ <a id="link" accesskey="l">link</a>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/actions/test_keys_menu.xul b/accessible/tests/mochitest/actions/test_keys_menu.xul
new file mode 100644
index 000000000..b63b6fab1
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_keys_menu.xul
@@ -0,0 +1,99 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL access keys and shortcut keys tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function openMenu(aMenuID, aMenuitemID)
+ {
+ this.menuNode = getNode(aMenuID),
+ this.menuitemNode = getNode(aMenuitemID),
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, this.menuNode)
+ ];
+
+ this.invoke = function openMenu_invoke()
+ {
+ // Show menu.
+ this.menuNode.open = true;
+ }
+
+ this.finalCheck = function openMenu_finalCheck()
+ {
+ var menu = getAccessible(aMenuID);
+ is(menu.accessKey, (MAC ? "u" : "Alt+u"),
+ "Wrong accesskey on " + prettyName(this.menuitemNode));
+
+ var menuitem = getAccessible(aMenuitemID);
+ is(menuitem.accessKey, "p",
+ "Wrong accesskey on " + prettyName(this.menuitemNode));
+ is(menuitem.keyboardShortcut, (MAC ? "⌃l" : "Ctrl+l"),
+ "Wrong keyboard shortcut on " + prettyName(this.menuitemNode));
+ }
+
+ this.getID = function openMenu_getID()
+ {
+ return "menuitem accesskey and shortcut test " +
+ prettyName(this.menuItemNode);
+ }
+ }
+
+ var gQueue = null;
+ function doTest()
+ {
+ gQueue = new eventQueue();
+ gQueue.push(new openMenu("menu", "menuitem"));
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=672092"
+ title="Reorganize access key and keyboard shortcut handling code">
+ Mozilla Bug 672092
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <keyset>
+ <key key="l" modifiers="control" id="key1"/>
+ </keyset>
+
+ <menubar>
+ <menu label="menu" id="menu" accesskey="u">
+ <menupopup>
+ <menuitem accesskey="p" key="key1" label="item1" id="menuitem"/>
+ </menupopup>
+ </menu>
+ </menubar>
+
+ <vbox id="debug"/>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/actions/test_link.html b/accessible/tests/mochitest/actions/test_link.html
new file mode 100644
index 000000000..dc96951d1
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_link.html
@@ -0,0 +1,147 @@
+<html>
+
+<head>
+ <title>nsIAccessible actions testing on HTML links (HTML:a)</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../actions.js"></script>
+
+ <script type="application/javascript">
+ function getAnchorTargetDocumentAcc()
+ {
+ var thisTabDocAcc = getTabDocAccessible();
+ var thisDocTabPanelAcc = thisTabDocAcc.parent.parent;
+ var tabPanelsAcc = thisDocTabPanelAcc.parent;
+ var newDocTabPanelAcc = tabPanelsAcc.firstChild;
+ var nextAcc = newDocTabPanelAcc;
+
+ while (nextAcc = nextAcc.nextSibling) {
+ // Find the last accessible for a browser with about:mozilla loaded.
+ if (nextAcc.firstChild.DOMNode.currentURI.spec == "about:mozilla") {
+ newDocTabPanelAcc = nextAcc;
+ }
+ }
+
+ return newDocTabPanelAcc.firstChild.firstChild;
+ }
+
+ function linkChecker(aID)
+ {
+ this.type = EVENT_DOCUMENT_LOAD_COMPLETE;
+ this.__defineGetter__("target", getAnchorTargetDocumentAcc);
+
+ this.check = function linkChecker_check()
+ {
+ var anchorTargetWindow =
+ getAccessible(getAnchorTargetDocumentAcc(), [nsIAccessibleDocument]).
+ window;
+ anchorTargetWindow.close();
+ }
+
+ this.getID = function linkChecker_getID()
+ {
+ return "link '" + aID + "' states check ";
+ }
+ }
+
+ //gA11yEventDumpToConsole = true;
+ //enableLogging("tree,eventTree,verbose");
+
+ function doTest()
+ {
+ var actionsArray = [
+ {
+ ID: "link1",
+ actionName: "jump",
+ events: CLICK_EVENTS,
+ eventSeq: [
+ new linkChecker("link1")
+ ]
+ },
+ {
+ ID: "img1",
+ targetID: "link1",
+ actionName: "jump",
+ events: CLICK_EVENTS,
+ eventSeq: [
+ new linkChecker("link1")
+ ]
+ },
+ {
+ ID: "link2",
+ actionName: "click",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "img2",
+ targetID: "link2",
+ actionName: "jump",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "link3",
+ actionName: "click",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "img3",
+ targetID: "link3",
+ actionName: "jump",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "link4",
+ actionName: "click",
+ events: CLICK_EVENTS
+ },
+ {
+ ID: "img4",
+ targetID: "link4",
+ actionName: "jump",
+ events: CLICK_EVENTS
+ }
+ ];
+ testActions(actionsArray);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=423409"
+ title="Expose click action if mouseup and mousedown are registered">
+ Mozilla Bug 423409
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <a href="about:mozilla" id="link1" target="_blank">
+ <img src="../moz.png" id="img1">
+ </a>
+ <a id="link2" onmousedown="">
+ <img src="../moz.png" id="img2">
+ </a>
+ <a id="link3" onclick="">
+ <img src="../moz.png" id="img3">
+ </a>
+ <a id="link4" onmouseup="">
+ <img src="../moz.png" id="img4">
+ </a>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/actions/test_media.html b/accessible/tests/mochitest/actions/test_media.html
new file mode 100644
index 000000000..beb014ebc
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_media.html
@@ -0,0 +1,121 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=483573
+-->
+<head>
+ <title>HTML5 audio/video tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../actions.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+
+ // gA11yEventDumpID = "eventDump";
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ function focusChecker(aAcc)
+ {
+ this.type = EVENT_FOCUS;
+ this.target = aAcc;
+ this.getID = function focusChecker_getID()
+ {
+ return "focus handling";
+ }
+ this.check = function focusChecker_check(aEvent)
+ {
+ testStates(this.target, STATE_FOCUSED);
+ }
+ }
+
+ function nameChecker(aAcc, aName)
+ {
+ this.type = EVENT_NAME_CHANGE;
+ this.target = aAcc;
+ this.getID = function nameChecker_getID()
+ {
+ return "name change handling";
+ },
+ this.check = function nameChecker_check(aEvent)
+ {
+ is(aEvent.accessible.name, aName,
+ "Wrong name of " + prettyName(aEvent.accessible) + " on focus");
+ }
+ }
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // test actions of audio controls
+
+ todo(false, "Focus test are disabled until bug 494175 is fixed.");
+
+ var audioElm = getAccessible("audio");
+ var playBtn = audioElm.firstChild;
+ var scrubber = playBtn.nextSibling.nextSibling.nextSibling;
+ var muteBtn = audioElm.lastChild.previousSibling;
+
+ var actions = [
+ {
+ ID: muteBtn,
+ actionName: "press",
+ events: CLICK_EVENTS,
+ eventSeq: [
+ // new focusChecker(muteBtn),
+ new nameChecker(muteBtn, "Unmute"),
+ ]
+ },
+ // {
+ // ID: scrubber,
+ // actionName: "activate",
+ // events: null,
+ // eventSeq: [
+ // new focusChecker(scrubber)
+ // ]
+ // },
+ {
+ ID: playBtn,
+ actionName: "press",
+ events: CLICK_EVENTS,
+ eventSeq: [
+ // new focusChecker(playBtn),
+ new nameChecker(playBtn, "Pause"),
+ ]
+ }
+ ];
+
+ testActions(actions); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Expose HTML5 video and audio elements' embedded controls through accessibility APIs"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=483573">Mozilla Bug 483573</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <audio id="audio" src="../bug461281.ogg"
+ controls="true"></audio>
+
+ <div id="eventDump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/actions/test_select.html b/accessible/tests/mochitest/actions/test_select.html
new file mode 100644
index 000000000..1a6c89619
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_select.html
@@ -0,0 +1,105 @@
+<html>
+
+<head>
+ <title>nsIAccessible actions testing for HTML select</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../actions.js"></script>
+
+ <script type="application/javascript">
+ //gA11yEventDumpToConsole = true; // debugging
+ function doTest()
+ {
+ var actionsArray = [
+ {
+ ID: "lb_apple",
+ actionIndex: 0,
+ actionName: "select",
+ events: CLICK_EVENTS,
+ eventSeq: [
+ new focusChecker("lb_apple")
+ ]
+ },
+ {
+ ID: "combobox",
+ actionIndex: 0,
+ actionName: "open",
+ events: CLICK_EVENTS,
+ eventSeq: [
+ new focusChecker("cb_orange")
+ ]
+ },
+ {
+ ID: "cb_apple",
+ actionIndex: 0,
+ actionName: "select",
+ events: CLICK_EVENTS,
+ eventSeq: [
+ new focusChecker("combobox")
+ ]
+ },
+ {
+ ID: "combobox",
+ actionIndex: 0,
+ actionName: "open",
+ events: CLICK_EVENTS,
+ eventSeq: [
+ new focusChecker("cb_apple")
+ ]
+ },
+ {
+ ID: "combobox",
+ actionIndex: 0,
+ actionName: "close",
+ events: CLICK_EVENTS,
+ eventSeq: [
+ new focusChecker("combobox")
+ ]
+ }
+ ];
+
+ testActions(actionsArray);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958"
+ title="Rework accessible focus handling">
+ Mozilla Bug 673958
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <select id="listbox" size="2">
+ <option id="lb_orange">orange</option>
+ <option id="lb_apple">apple</option>
+ </select>
+
+ <select id="combobox">
+ <option id="cb_orange">orange</option>
+ <option id="cb_apple">apple</option>
+ </select>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/actions/test_tree.xul b/accessible/tests/mochitest/actions/test_tree.xul
new file mode 100644
index 000000000..3958ed2fb
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_tree.xul
@@ -0,0 +1,128 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL tree actions tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../actions.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Accessible tree testers
+
+ function stateFocusChecker(aAcc, aStates)
+ {
+ this.__proto__ = new focusChecker(aAcc);
+
+ this.check = function focusChecker_check(aEvent)
+ {
+ var states = aStates ? aStates : 0;
+ testStates(this.target, STATE_FOCUSED | STATE_SELECTED | states);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "debug";
+ //gA11yEventDumpToConsole = true; // debug
+
+ function doTest()
+ {
+ var treeNode = getNode("tree");
+
+ var treeBodyNode = treeNode.boxObject.treeBody;
+
+ var tree = getAccessible(treeNode);
+ var expandedTreeItem = tree.getChildAt(2);
+ var collapsedTreeItem = tree.getChildAt(5);
+
+ var actions = [
+ {
+ ID: expandedTreeItem,
+ actionName: "activate",
+ actionIndex: 0,
+ events: CLICK_EVENTS,
+ targetID: treeBodyNode,
+ eventSeq: [
+ new stateFocusChecker(expandedTreeItem, STATE_EXPANDED)
+ ]
+ },
+ {
+ ID: collapsedTreeItem,
+ actionName: "expand",
+ actionIndex: 1,
+ events: CLICK_EVENTS,
+ targetID: treeBodyNode,
+ checkOnClickEvent: function check(aEvent)
+ {
+ testStates(this.ID, STATE_EXPANDED);
+ }
+ },
+ {
+ ID: collapsedTreeItem,
+ actionName: "collapse",
+ actionIndex: 1,
+ events: CLICK_EVENTS,
+ targetID: treeBodyNode,
+ checkOnClickEvent: function check(aEvent)
+ {
+ testStates(this.ID, STATE_COLLAPSED);
+ }
+ }
+ ];
+
+ testActions(actions); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yXULTreeLoadEvent(doTest, "tree", new nsTreeTreeView());
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727"
+ title="Reorganize implementation of XUL tree accessibility">
+ Mozilla Bug 503727
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <tree id="tree" flex="1" minheight="100px">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <vbox id="debug"/>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/actions/test_treegrid.xul b/accessible/tests/mochitest/actions/test_treegrid.xul
new file mode 100644
index 000000000..0bfb3d662
--- /dev/null
+++ b/accessible/tests/mochitest/actions/test_treegrid.xul
@@ -0,0 +1,197 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<?xml-stylesheet href="../treeview.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL tree actions tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../actions.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Accessible tree testers
+
+ function focusChecker(aAcc, aStates)
+ {
+ this.type = EVENT_FOCUS;
+ this.target = aAcc;
+ this.getID = function focusChecker_getID()
+ {
+ return "focus handling";
+ }
+ this.check = function focusChecker_check(aEvent)
+ {
+ var states = aStates ? aStates : 0;
+ testStates(this.target, STATE_FOCUSED | STATE_SELECTED | states);
+ }
+ }
+
+ function stateChangeChecker(aAcc, aIsEnabled)
+ {
+ this.type = EVENT_STATE_CHANGE;
+ this.target = aAcc;
+ this.getID = function stateChangeChecker_getID()
+ {
+ return "state change handling";
+ }
+ this.check = function stateChangeChecker_check(aEvent)
+ {
+ if (aIsEnabled)
+ testStates(this.target, STATE_CHECKED);
+ else
+ testStates(this.target, STATE_CHECKABLE, 0, STATE_CHECKED);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function doTestActions()
+ {
+ var treeNode = getNode("tabletree");
+
+ var treeBodyNode = treeNode.boxObject.treeBody;
+ treeNode.focus();
+
+ var tree = getAccessible(treeNode);
+
+ var expandedTreeItem = tree.getChildAt(2);
+ var collapsedTreeItem = tree.getChildAt(5);
+ var cycleCell = expandedTreeItem.getChildAt(0);
+ var checkableCell = expandedTreeItem.getChildAt(3);
+
+ var actions = [
+ {
+ ID: expandedTreeItem,
+ actionName: "activate",
+ actionIndex: 0,
+ events: CLICK_EVENTS,
+ targetID: treeBodyNode,
+ eventSeq: [
+ new focusChecker(expandedTreeItem, STATE_EXPANDED)
+ ]
+ },
+ {
+ ID: collapsedTreeItem,
+ actionName: "expand",
+ actionIndex: 1,
+ events: CLICK_EVENTS,
+ targetID: treeBodyNode,
+ check: function check(aEvent)
+ {
+ testStates(this.ID, STATE_EXPANDED);
+ }
+ },
+ {
+ ID: collapsedTreeItem,
+ actionName: "collapse",
+ actionIndex: 1,
+ events: CLICK_EVENTS,
+ targetID: treeBodyNode,
+ check: function check(aEvent)
+ {
+ testStates(this.ID, STATE_COLLAPSED);
+ }
+ },
+ {
+ ID: cycleCell,
+ actionName: "cycle",
+ actionIndex: 0,
+ events: CLICK_EVENTS,
+ targetID: treeBodyNode
+ },
+ {
+ ID: checkableCell,
+ actionName: "uncheck",
+ actionIndex: 0,
+ events: CLICK_EVENTS,
+ targetID: treeBodyNode,
+ eventSeq: [
+ new stateChangeChecker(checkableCell, false)
+ ]
+ },
+ {
+ ID: checkableCell,
+ actionName: "check",
+ actionIndex: 0,
+ events: CLICK_EVENTS,
+ targetID: treeBodyNode,
+ eventSeq: [
+ new stateChangeChecker(checkableCell, true)
+ ]
+ }
+ ];
+
+ testActions(actions); // Will call SimpleTest.finish();
+ }
+
+ // gA11yEventDumpID = "debug";
+
+ function doTest()
+ {
+ var treeNode = getNode("tabletree");
+ waitForEvent(EVENT_REORDER, treeNode, doTestActions);
+ treeNode.view = new nsTreeTreeView();
+ }
+
+ function test1()
+ {
+ getNode("tabletree").view.setCellValue(0, boxObj.columns.firstColumn, "false");
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727"
+ title="Reorganize implementation of XUL tree accessibility">
+ Mozilla Bug 503727
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <tree id="tabletree" flex="1" editable="true">
+ <treecols>
+ <treecol id="tabletree_col1" cycler="true" label="cycler"/>
+ <treecol id="tabletree_col2" flex="1" primary="true" label="column1"/>
+ <treecol id="tabletree_col3" flex="1" label="column2"/>
+ <treecol id="tabletree_col4" flex="1" label="checker"
+ type="checkbox" editable="true"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <vbox id="debug"/>
+ <button oncommand="test1();" label="uncheck"/>
+ </vbox>
+ </hbox>
+
+</window>
diff --git a/accessible/tests/mochitest/aom/a11y.ini b/accessible/tests/mochitest/aom/a11y.ini
new file mode 100644
index 000000000..03085c1de
--- /dev/null
+++ b/accessible/tests/mochitest/aom/a11y.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+[test_general.html]
diff --git a/accessible/tests/mochitest/aom/test_general.html b/accessible/tests/mochitest/aom/test_general.html
new file mode 100644
index 000000000..5812ac55f
--- /dev/null
+++ b/accessible/tests/mochitest/aom/test_general.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Accessibility API: generic</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script>
+ 'use strict';
+
+ SimpleTest.waitForExplicitFinish();
+ const finish = SimpleTest.finish.bind(SimpleTest);
+ enablePref()
+ .then(createIframe)
+ .then(checkImplementation)
+ .catch(err => {
+ dump(`${err}: ${err.stack}`);
+ finish();
+ });
+
+ function enablePref() {
+ const ops = {
+ "set": [
+ [ "accessibility.AOM.enabled", true ],
+ ],
+ };
+ return SpecialPowers.pushPrefEnv(ops);
+ }
+
+ // WebIDL conditional annotations for an interface are evaluated once per
+ // global, so we need to create an iframe to see the effects of calling
+ // enablePref().
+ function createIframe() {
+ return new Promise((resolve) => {
+ let iframe = document.createElement("iframe");
+ iframe.src = "about:blank";
+ iframe.onload = () => resolve(iframe.contentDocument);
+ document.body.appendChild(iframe);
+ });
+ }
+
+ // Check that the WebIDL is as expected.
+ function checkImplementation(ifrDoc) {
+ let anode = ifrDoc.accessibleNode;
+ ok(anode, "DOM document has accessible node");
+
+ is(anode.role, 'document', 'correct role of a document accessible node');
+ is(anode.DOMNode, ifrDoc, 'correct DOM Node of a document accessible node');
+
+ finish();
+ }
+ </script>
+</head>
diff --git a/accessible/tests/mochitest/attributes.js b/accessible/tests/mochitest/attributes.js
new file mode 100644
index 000000000..b2ac78cba
--- /dev/null
+++ b/accessible/tests/mochitest/attributes.js
@@ -0,0 +1,382 @@
+////////////////////////////////////////////////////////////////////////////////
+// Object attributes.
+
+/**
+ * Test object attributes.
+ *
+ * @param aAccOrElmOrID [in] the accessible identifier
+ * @param aAttrs [in] the map of expected object attributes
+ * (name/value pairs)
+ * @param aSkipUnexpectedAttrs [in] points this function doesn't fail if
+ * unexpected attribute is encountered
+ */
+function testAttrs(aAccOrElmOrID, aAttrs, aSkipUnexpectedAttrs)
+{
+ testAttrsInternal(aAccOrElmOrID, aAttrs, aSkipUnexpectedAttrs);
+}
+
+/**
+ * Test object attributes that must not be present.
+ *
+ * @param aAccOrElmOrID [in] the accessible identifier
+ * @param aAbsentAttrs [in] map of attributes that should not be
+ * present (name/value pairs)
+ */
+function testAbsentAttrs(aAccOrElmOrID, aAbsentAttrs)
+{
+ testAttrsInternal(aAccOrElmOrID, {}, true, aAbsentAttrs);
+}
+
+/**
+ * Test CSS based object attributes.
+ */
+function testCSSAttrs(aID)
+{
+ var node = document.getElementById(aID);
+ var computedStyle = document.defaultView.getComputedStyle(node, "");
+
+ var attrs = {
+ "display": computedStyle.display,
+ "text-align": computedStyle.textAlign,
+ "text-indent": computedStyle.textIndent,
+ "margin-left": computedStyle.marginLeft,
+ "margin-right": computedStyle.marginRight,
+ "margin-top": computedStyle.marginTop,
+ "margin-bottom": computedStyle.marginBottom
+ };
+ testAttrs(aID, attrs, true);
+}
+
+/**
+ * Test the accessible that it doesn't have CSS-based object attributes.
+ */
+function testAbsentCSSAttrs(aID)
+{
+ var attrs = {
+ "display": "",
+ "text-align": "",
+ "text-indent": "",
+ "margin-left": "",
+ "margin-right": "",
+ "margin-top": "",
+ "margin-bottom": ""
+ };
+ testAbsentAttrs(aID, attrs);
+}
+
+/**
+ * Test group object attributes (posinset, setsize and level) and
+ * nsIAccessible::groupPosition() method.
+ *
+ * @param aAccOrElmOrID [in] the ID, DOM node or accessible
+ * @param aPosInSet [in] the value of 'posinset' attribute
+ * @param aSetSize [in] the value of 'setsize' attribute
+ * @param aLevel [in, optional] the value of 'level' attribute
+ */
+function testGroupAttrs(aAccOrElmOrID, aPosInSet, aSetSize, aLevel)
+{
+ var acc = getAccessible(aAccOrElmOrID);
+ var levelObj = {}, posInSetObj = {}, setSizeObj = {};
+ acc.groupPosition(levelObj, setSizeObj, posInSetObj);
+
+ if (aPosInSet && aSetSize) {
+ is(posInSetObj.value, aPosInSet,
+ "Wrong group position (posinset) for " + prettyName(aAccOrElmOrID));
+ is(setSizeObj.value, aSetSize,
+ "Wrong size of the group (setsize) for " + prettyName(aAccOrElmOrID));
+
+ var attrs = {
+ "posinset": String(aPosInSet),
+ "setsize": String(aSetSize)
+ };
+ testAttrs(aAccOrElmOrID, attrs, true);
+ }
+
+ if (aLevel) {
+ is(levelObj.value, aLevel,
+ "Wrong group level for " + prettyName(aAccOrElmOrID));
+
+ var attrs = { "level" : String(aLevel) };
+ testAttrs(aAccOrElmOrID, attrs, true);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Text attributes.
+
+/**
+ * Test text attributes.
+ *
+ * @param aID [in] the ID of DOM element having text
+ * accessible
+ * @param aOffset [in] the offset inside text accessible to fetch
+ * text attributes
+ * @param aAttrs [in] the map of expected text attributes
+ * (name/value pairs) exposed at the offset
+ * @param aDefAttrs [in] the map of expected text attributes
+ * (name/value pairs) exposed on hyper text
+ * accessible
+ * @param aStartOffset [in] expected start offset where text attributes
+ * are applied
+ * @param aEndOffset [in] expected end offset where text attribute
+ * are applied
+ * @param aSkipUnexpectedAttrs [in] points the function doesn't fail if
+ * unexpected attribute is encountered
+ */
+function testTextAttrs(aID, aOffset, aAttrs, aDefAttrs,
+ aStartOffset, aEndOffset, aSkipUnexpectedAttrs)
+{
+ var accessible = getAccessible(aID, [nsIAccessibleText]);
+ if (!accessible)
+ return;
+
+ var startOffset = { value: -1 };
+ var endOffset = { value: -1 };
+
+ // do not include attributes exposed on hyper text accessbile
+ var attrs = getTextAttributes(aID, accessible, false, aOffset,
+ startOffset, endOffset);
+
+ if (!attrs)
+ return;
+
+ var errorMsg = " for " + aID + " at offset " + aOffset;
+
+ is(startOffset.value, aStartOffset, "Wrong start offset" + errorMsg);
+ is(endOffset.value, aEndOffset, "Wrong end offset" + errorMsg);
+
+ compareAttrs(errorMsg, attrs, aAttrs, aSkipUnexpectedAttrs);
+
+ // include attributes exposed on hyper text accessbile
+ var expectedAttrs = {};
+ for (var name in aAttrs)
+ expectedAttrs[name] = aAttrs[name];
+
+ for (var name in aDefAttrs) {
+ if (!(name in expectedAttrs))
+ expectedAttrs[name] = aDefAttrs[name];
+ }
+
+ attrs = getTextAttributes(aID, accessible, true, aOffset,
+ startOffset, endOffset);
+
+ if (!attrs)
+ return;
+
+ compareAttrs(errorMsg, attrs, expectedAttrs, aSkipUnexpectedAttrs);
+}
+
+/**
+ * Test default text attributes.
+ *
+ * @param aID [in] the ID of DOM element having text
+ * accessible
+ * @param aDefAttrs [in] the map of expected text attributes
+ * (name/value pairs)
+ * @param aSkipUnexpectedAttrs [in] points the function doesn't fail if
+ * unexpected attribute is encountered
+ */
+function testDefaultTextAttrs(aID, aDefAttrs, aSkipUnexpectedAttrs)
+{
+ var accessible = getAccessible(aID, [nsIAccessibleText]);
+ if (!accessible)
+ return;
+
+ var defAttrs = null;
+ try{
+ defAttrs = accessible.defaultTextAttributes;
+ } catch (e) {
+ }
+
+ if (!defAttrs) {
+ ok(false, "Can't get default text attributes for " + aID);
+ return;
+ }
+
+ var errorMsg = ". Getting default text attributes for " + aID;
+ compareAttrs(errorMsg, defAttrs, aDefAttrs, aSkipUnexpectedAttrs);
+}
+
+/**
+ * Test text attributes for wrong offset.
+ */
+function testTextAttrsWrongOffset(aID, aOffset)
+{
+ var res = false;
+ try {
+ var s = {}, e = {};
+ var acc = getAccessible(ID, [nsIAccessibleText]);
+ acc.getTextAttributes(false, 157, s, e);
+ } catch (e) {
+ res = true;
+ }
+
+ ok(res,
+ "text attributes are calculated successfully at wrong offset " + aOffset + " for " + prettyName(aID));
+}
+
+const kNormalFontWeight =
+ function equalsToNormal(aWeight) { return aWeight <= 400 ; }
+
+const kBoldFontWeight =
+ function equalsToBold(aWeight) { return aWeight > 400; }
+
+// The pt font size of the input element can vary by Linux distro.
+const kInputFontSize = WIN ?
+ "10pt" : (MAC ? "8pt" : function() { return true; });
+
+const kAbsentFontFamily =
+ function(aFontFamily) { return aFontFamily != "sans-serif"; }
+const kInputFontFamily =
+ function(aFontFamily) { return aFontFamily != "sans-serif"; }
+
+const kMonospaceFontFamily =
+ function(aFontFamily) { return aFontFamily != "monospace"; }
+const kSansSerifFontFamily =
+ function(aFontFamily) { return aFontFamily != "sans-serif"; }
+const kSerifFontFamily =
+ function(aFontFamily) { return aFontFamily != "serif"; }
+
+const kCursiveFontFamily = LINUX ? "DejaVu Serif" : "Comic Sans MS";
+
+/**
+ * Return used font from the given computed style.
+ */
+function fontFamily(aComputedStyle)
+{
+ var name = aComputedStyle.fontFamily;
+ switch (name) {
+ case "monospace":
+ return kMonospaceFontFamily;
+ case "sans-serif":
+ return kSansSerifFontFamily;
+ case "serif":
+ return kSerifFontFamily;
+ default:
+ return name;
+ }
+}
+
+/**
+ * Build an object of default text attributes expected for the given accessible.
+ *
+ * @param aID [in] identifier of accessible
+ * @param aFontSize [in] font size
+ * @param aFontWeight [in, optional] kBoldFontWeight or kNormalFontWeight,
+ * default value is kNormalFontWeight
+ */
+function buildDefaultTextAttrs(aID, aFontSize, aFontWeight, aFontFamily)
+{
+ var elm = getNode(aID);
+ var computedStyle = document.defaultView.getComputedStyle(elm, "");
+ var bgColor = computedStyle.backgroundColor == "transparent" ?
+ "rgb(255, 255, 255)" : computedStyle.backgroundColor;
+
+ var defAttrs = {
+ "font-style": computedStyle.fontStyle,
+ "font-size": aFontSize,
+ "background-color": bgColor,
+ "font-weight": aFontWeight ? aFontWeight : kNormalFontWeight,
+ "color": computedStyle.color,
+ "font-family": aFontFamily ? aFontFamily : fontFamily(computedStyle),
+ "text-position": computedStyle.verticalAlign
+ };
+
+ return defAttrs;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Private.
+
+function getTextAttributes(aID, aAccessible, aIncludeDefAttrs, aOffset,
+ aStartOffset, aEndOffset)
+{
+ // This function expects the passed in accessible to already be queried for
+ // nsIAccessibleText.
+ var attrs = null;
+ try {
+ attrs = aAccessible.getTextAttributes(aIncludeDefAttrs, aOffset,
+ aStartOffset, aEndOffset);
+ } catch (e) {
+ }
+
+ if (attrs)
+ return attrs;
+
+ ok(false, "Can't get text attributes for " + aID);
+ return null;
+}
+
+function testAttrsInternal(aAccOrElmOrID, aAttrs, aSkipUnexpectedAttrs,
+ aAbsentAttrs)
+{
+ var accessible = getAccessible(aAccOrElmOrID);
+ if (!accessible)
+ return;
+
+ var attrs = null;
+ try {
+ attrs = accessible.attributes;
+ } catch (e) { }
+
+ if (!attrs) {
+ ok(false, "Can't get object attributes for " + prettyName(aAccOrElmOrID));
+ return;
+ }
+
+ var errorMsg = " for " + prettyName(aAccOrElmOrID);
+ compareAttrs(errorMsg, attrs, aAttrs, aSkipUnexpectedAttrs, aAbsentAttrs);
+}
+
+function compareAttrs(aErrorMsg, aAttrs, aExpectedAttrs, aSkipUnexpectedAttrs,
+ aAbsentAttrs)
+{
+ // Check if all obtained attributes are expected and have expected value.
+ var enumerate = aAttrs.enumerate();
+ while (enumerate.hasMoreElements()) {
+ var prop = enumerate.getNext().QueryInterface(nsIPropertyElement);
+
+ if (!(prop.key in aExpectedAttrs)) {
+ if (!aSkipUnexpectedAttrs)
+ ok(false, "Unexpected attribute '" + prop.key + "' having '" +
+ prop.value + "'" + aErrorMsg);
+ } else {
+ var msg = "Attribute '" + prop.key + "' has wrong value" + aErrorMsg;
+ var expectedValue = aExpectedAttrs[prop.key];
+
+ if (typeof expectedValue == "function")
+ ok(expectedValue(prop.value), msg);
+ else
+ is(prop.value, expectedValue, msg);
+ }
+ }
+
+ // Check if all expected attributes are presented.
+ for (var name in aExpectedAttrs) {
+ var value = "";
+ try {
+ value = aAttrs.getStringProperty(name);
+ } catch(e) { }
+
+ if (!value)
+ ok(false,
+ "There is no expected attribute '" + name + "' " + aErrorMsg);
+ }
+
+ // Check if all unexpected attributes are absent.
+ if (aAbsentAttrs) {
+ for (var name in aAbsentAttrs) {
+ var wasFound = false;
+
+ var enumerate = aAttrs.enumerate();
+ while (enumerate.hasMoreElements()) {
+ var prop = enumerate.getNext().QueryInterface(nsIPropertyElement);
+ if (prop.key == name)
+ wasFound = true;
+ }
+ }
+
+ ok(!wasFound,
+ "There is an unexpected attribute '" + name + "' " + aErrorMsg);
+ }
+}
diff --git a/accessible/tests/mochitest/attributes/a11y.ini b/accessible/tests/mochitest/attributes/a11y.ini
new file mode 100644
index 000000000..ad53ecd27
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/a11y.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+
+[test_obj.html]
+[test_obj_css.html]
+[test_obj_css.xul]
+[test_obj_group.html]
+[test_obj_group.xul]
+[test_obj_group_tree.xul]
+[test_tag.html]
+[test_xml-roles.html]
diff --git a/accessible/tests/mochitest/attributes/test_obj.html b/accessible/tests/mochitest/attributes/test_obj.html
new file mode 100644
index 000000000..9e147e1d1
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/test_obj.html
@@ -0,0 +1,278 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=475006
+https://bugzilla.mozilla.org/show_bug.cgi?id=391829
+https://bugzilla.mozilla.org/show_bug.cgi?id=581952
+https://bugzilla.mozilla.org/show_bug.cgi?id=558036
+-->
+<head>
+ <title>Group attributes tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ // aria
+ testAttrs("atomic", {"atomic" : "true", "container-atomic" : "true"}, true);
+ testAttrs(getNode("atomic").firstChild, {"container-atomic" : "true"}, true);
+ testAbsentAttrs("atomic_false", {"atomic" : "false", "container-atomic" : "false"});
+ testAbsentAttrs(getNode("atomic_false").firstChild, {"container-atomic" : "false"});
+
+ testAttrs("autocomplete", {"autocomplete" : "true"}, true);
+ testAttrs("checkbox", {"checkable" : "true"}, true);
+ testAttrs("checkedCheckbox", {"checkable" : "true"}, true);
+ testAttrs("checkedMenuitem", {"checkable" : "true"}, true);
+ testAttrs("checkedOption", {"checkable" : "true"}, true);
+ testAttrs("checkedRadio", {"checkable" : "true"}, true);
+ testAttrs("checkedTreeitem", {"checkable" : "true"}, true);
+ testAttrs("dropeffect", {"dropeffect" : "copy"}, true);
+ testAttrs("grabbed", {"grabbed" : "true"}, true);
+ testAbsentAttrs("haspopup", { "haspopup": "false" });
+ testAttrs("hidden", {"hidden" : "true"}, true);
+ testAbsentAttrs("hidden_false", { "hidden": "false" });
+ testAbsentAttrs("modal", {"modal" : "true"});
+ testAttrs("sortAscending", {"sort" : "ascending"}, true);
+ testAttrs("sortDescending", {"sort" : "descending"}, true);
+ testAttrs("sortNone", {"sort" : "none"}, true);
+ testAttrs("sortOther", {"sort" : "other"}, true);
+ testAttrs("roledescr", {"roledescription" : "spreadshit"}, true);
+ testAttrs("currentPage", {"current" : "page"}, true);
+
+ // inherited attributes by subdocuments
+ var subdoc = getAccessible("iframe").firstChild;
+ testAttrs(subdoc, {"busy" : "true"}, true);
+
+ // live object attribute
+
+ // HTML
+ testAttrs("output", {"live" : "polite"}, true);
+
+ // ARIA
+ testAttrs("live", {"live" : "polite"}, true);
+ testAttrs("live2", {"live" : "polite"}, true);
+ testAbsentAttrs("live3", {"live" : ""});
+ testAttrs("log", {"live" : "polite"}, true);
+ testAttrs("logAssertive", {"live" : "assertive"}, true);
+ testAttrs("marquee", {"live" : "off"}, true);
+ testAttrs("status", {"live" : "polite"}, true);
+ testAttrs("timer", {"live" : "off"}, true);
+ testAbsentAttrs("tablist", {"live" : "polite"});
+
+ // container-live object attribute
+ testAttrs("liveChild", {"container-live" : "polite"}, true);
+ testAttrs("live2Child", {"container-live" : "polite"}, true);
+ testAttrs("logChild", {"container-live" : "polite"}, true);
+ testAttrs("logAssertiveChild", {"container-live" : "assertive"}, true);
+ testAttrs("marqueeChild", {"container-live" : "off"}, true);
+ testAttrs("statusChild", {"container-live" : "polite"}, true);
+ testAttrs("timerChild", {"container-live" : "off"}, true);
+ testAbsentAttrs("tablistChild", {"container-live" : "polite"});
+
+ // container-live-role object attribute
+ testAttrs("log", {"container-live-role" : "log"}, true);
+ testAttrs("logAssertive", {"container-live-role" : "log"}, true);
+ testAttrs("marquee", {"container-live-role" : "marquee"}, true);
+ testAttrs("status", {"container-live-role" : "status"}, true);
+ testAttrs("timer", {"container-live-role" : "timer"}, true);
+ testAttrs("logChild", {"container-live-role" : "log"}, true);
+ testAttrs("logAssertive", {"container-live-role" : "log"}, true);
+ testAttrs("logAssertiveChild", {"container-live-role" : "log"}, true);
+ testAttrs("marqueeChild", {"container-live-role" : "marquee"}, true);
+ testAttrs("statusChild", {"container-live-role" : "status"}, true);
+ testAttrs("timerChild", {"container-live-role" : "timer"}, true);
+ testAbsentAttrs("tablistChild", {"container-live-role" : "tablist"});
+
+ // absent aria-label and aria-labelledby object attribute
+ testAbsentAttrs("label", {"label" : "foo"});
+ testAbsentAttrs("labelledby", {"labelledby" : "label"});
+
+ // container that has no default live attribute
+ testAttrs("liveGroup", {"live" : "polite"}, true);
+ testAttrs("liveGroupChild", {"container-live" : "polite"}, true);
+ testAttrs("liveGroup", {"container-live-role" : "group"}, true);
+ testAttrs("liveGroupChild", {"container-live-role" : "group"}, true);
+
+ // text input type
+ testAbsentAttrs("button", { "text-input-type": "button"});
+ testAbsentAttrs("checkbox", { "text-input-type": "checkbox"});
+ testAbsentAttrs("radio", { "text-input-type": "radio"});
+ testAttrs("email", {"text-input-type" : "email"}, true);
+ testAttrs("search", {"text-input-type" : "search"}, true);
+ testAttrs("tel", {"text-input-type" : "tel"}, true);
+ testAttrs("url", {"text-input-type" : "url"}, true);
+
+ // ARIA
+ testAttrs("searchbox", {"text-input-type" : "search"}, true);
+
+ // html
+ testAttrs("radio", {"checkable" : "true"}, true);
+ testAttrs("checkbox", {"checkable" : "true"}, true);
+ testAttrs("draggable", {"draggable" : "true"}, true);
+ testAttrs("th1", { "abbr": "SS#" }, true);
+ testAttrs("th2", { "abbr": "SS#" }, true);
+ testAttrs("th2", { "axis": "social" }, true);
+
+ // don't barf on an empty abbr element.
+ testAbsentAttrs("th3", { "abbr": "" }, true);
+
+ // application accessible
+ if (WIN) {
+ var gfxInfo = Components.classes["@mozilla.org/gfx/info;1"].
+ getService(Components.interfaces.nsIGfxInfo);
+ var attrs = {
+ "D2D": (gfxInfo.D2DEnabled ? "true" : "false")
+ }
+ testAttrs(getApplicationAccessible(), attrs, false);
+ }
+
+ // no object attributes
+ testAbsentAttrs(getAccessible("listitem").firstChild, { "tag": "" });
+
+ // experimental aria
+ testAttrs("experimental", {"blah" : "true"}, true);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=475006"
+ title="Extend nsARIAMap to capture ARIA attribute characteristics">
+ Mozilla Bug 475006
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=391829"
+ title="Add support for container-live-role to object attributes">
+ Mozilla Bug 391829
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=581952"
+ title="Make explicit that aria-label is not an object attribute">
+ Mozilla Bug 475006
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=558036"
+ title="make HTML <output> accessible">
+ Mozilla Bug 558036
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=896400"
+ title="Tablist should no longer be an implicit live region">
+ Mozilla Bug 896400
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=563862"
+ title="Expand support for nsIAccessibleEvent::OBJECT_ATTRIBUTE_CHANGE">
+ Mozilla Bug 563862
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=819303"
+ title="crash in nsTextEquivUtils::AppendTextEquivFromTextContent">
+ Mozilla Bug 819303
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=838407"
+ title="aria-hidden false value shouldn't be exposed via object attributes">
+ Mozilla Bug 838407
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1121518"
+ title="ARIA 1.1: Support role 'searchbox'">
+ Mozilla Bug 1121518
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- aria -->
+ <div id="atomic" aria-atomic="true">live region</div>
+ <div id="atomic_false" aria-atomic="false">live region</div>
+ <div id="autocomplete" role="textbox" aria-autocomplete="true"></div>
+ <div id="checkbox" role="checkbox"></div>
+ <div id="checkedCheckbox" role="checkbox" aria-checked="true"></div>
+ <div id="checkedMenuitem" role="menuitem" aria-checked="true"></div>
+ <div id="checkedOption" role="option" aria-checked="true"></div>
+ <div id="checkedRadio" role="radio" aria-checked="true"></div>
+ <div id="checkedTreeitem" role="treeitem" aria-checked="true"></div>
+ <div id="dropeffect" aria-dropeffect="copy"></div>
+ <div id="grabbed" aria-grabbed="true"></div>
+ <div id="haspopup" aria-haspopup="true"></div>
+ <div id="hidden" aria-hidden="true"></div>
+ <div id="hidden_false" aria-hidden="false"></div>
+ <div id="modal" aria-modal="true"></div>
+ <div id="sortAscending" role="columnheader" aria-sort="ascending"></div>
+ <div id="sortDescending" role="columnheader" aria-sort="descending"></div>
+ <div id="sortNone" role="columnheader" aria-sort="none"></div>
+ <div id="sortOther" role="columnheader" aria-sort="other"></div>
+ <div id="roledescr" aria-roledescription="spreadshit"></div>
+ <div id="currentPage" aria-current="page"></div>
+
+ <!-- inherited from iframe -->
+ <iframe id="iframe" src="data:text/html,<html><body></body></html>"
+ aria-busy="true"></iframe>
+
+ <!-- html -->
+ <output id="output"></output>
+
+ <!-- back to aria -->
+ <div id="live" aria-live="polite">excuse <div id="liveChild">me</div></div>
+ <div id="live2" role="marquee" aria-live="polite">excuse <div id="live2Child">me</div></div>
+ <div id="live3" role="region">excuse</div>
+ <div id="log" role="log">excuse <div id="logChild">me</div></div>
+ <div id="logAssertive" role="log" aria-live="assertive">excuse <div id="logAssertiveChild">me</div></div>
+ <div id="marquee" role="marquee">excuse <div id="marqueeChild">me</div></div>
+ <div id="status" role="status">excuse <div id="statusChild">me</div></div>
+ <div id="tablist" role="tablist">tablist <div id="tablistChild">tab</div></div>
+ <div id="timer" role="timer">excuse <div id="timerChild">me</div></div>
+
+ <!-- aria-label[ledby] should not be an object attribute -->
+ <div id="label" role="checkbox" aria-label="foo"></div>
+ <div id="labelledby" role="checkbox" aria-labelledby="label"></div>
+
+ <!-- unusual live case -->
+ <div id="liveGroup" role="group" aria-live="polite">
+ excuse <div id="liveGroupChild">me</div>
+ </div>
+
+ <!-- text input type -->
+ <input id="button" type="button"/>
+ <input id="email" type="email"/>
+ <input id="search" type="search"/>
+ <input id="tel" type="tel"/>
+ <input id="url" type="url"/>
+ <div id="searchbox" role="searchbox"></div>
+
+ <!-- html -->
+ <input id="radio" type="radio"/>
+ <input id="checkbox" type="checkbox"/>
+ <div id="draggable" draggable="true">Draggable div</div>
+ <table>
+ <tr>
+ <th id="th1"><abbr title="Social Security Number">SS#</abbr></th>
+ <th id="th2" abbr="SS#" axis="social">Social Security Number</th>
+ <th id="th3"><abbr></abbr></th>
+ </tr>
+ </table>
+
+ <ul>
+ <li id="listitem">item
+ </ul>
+
+ <!-- experimental aria -->
+ <div id="experimental" aria-blah="true">Fake beer</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/attributes/test_obj_css.html b/accessible/tests/mochitest/attributes/test_obj_css.html
new file mode 100644
index 000000000..6bf34543f
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/test_obj_css.html
@@ -0,0 +1,231 @@
+<html>
+<head>
+ <title>CSS-like attributes tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ var gQueue = null;
+
+ function removeElm(aID)
+ {
+ this.node = getNode(aID);
+ this.accessible = getAccessible(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, this.accessible)
+ ];
+
+ this.invoke = function removeElm_invoke()
+ {
+ this.node.parentNode.removeChild(this.node);
+ }
+
+ this.check = function removeElm_check()
+ {
+ testAbsentCSSAttrs(this.accessible);
+ }
+
+ this.getID = function removeElm_getID()
+ {
+ return "test CSS-based attributes on removed accessible";
+ }
+ }
+
+ function doTest()
+ {
+ // CSS display
+ testCSSAttrs("display_block");
+ testCSSAttrs("display_inline");
+ testCSSAttrs("display_inline-block");
+ testCSSAttrs("display_list-item");
+ testCSSAttrs("display_table");
+ testCSSAttrs("display_inline-table");
+ testCSSAttrs("display_table-row-group");
+ testCSSAttrs("display_table-column");
+ testCSSAttrs("display_table-column-group");
+ testCSSAttrs("display_table-header-group");
+ testCSSAttrs("display_table-footer-group");
+ testCSSAttrs("display_table-row");
+ testCSSAttrs("display_table-cell");
+ testCSSAttrs("display_table-caption");
+
+ // CSS text-align
+ testCSSAttrs("text-align_left");
+ testCSSAttrs("text-align_right");
+ testCSSAttrs("text-align_center");
+ testCSSAttrs("text-align_justify");
+ testCSSAttrs("text-align_inherit");
+
+ // CSS text-indent
+ testCSSAttrs("text-indent_em");
+ testCSSAttrs("text-indent_ex");
+ testCSSAttrs("text-indent_in");
+ testCSSAttrs("text-indent_cm");
+ testCSSAttrs("text-indent_mm");
+ testCSSAttrs("text-indent_pt");
+ testCSSAttrs("text-indent_pc");
+ testCSSAttrs("text-indent_px");
+ testCSSAttrs("text-indent_percent");
+ testCSSAttrs("text-indent_inherit");
+
+ // CSS margin
+ testCSSAttrs("margin_em");
+ testCSSAttrs("margin_ex");
+ testCSSAttrs("margin_in");
+ testCSSAttrs("margin_cm");
+ testCSSAttrs("margin_mm");
+ testCSSAttrs("margin_pt");
+ testCSSAttrs("margin_pc");
+ testCSSAttrs("margin_px");
+ testCSSAttrs("margin_percent");
+ testCSSAttrs("margin_auto");
+ testCSSAttrs("margin_inherit");
+
+ testCSSAttrs("margin-left");
+ testCSSAttrs("margin-right");
+ testCSSAttrs("margin-top");
+ testCSSAttrs("margin-bottom");
+
+ // Elements
+ testCSSAttrs("span");
+ testCSSAttrs("div");
+ testCSSAttrs("p");
+ testCSSAttrs("input");
+ testCSSAttrs("table");
+ testCSSAttrs("tr");
+ testCSSAttrs("td");
+
+ // no CSS-based object attributes
+ testAbsentCSSAttrs(getAccessible("listitem").firstChild);
+
+ gQueue = new eventQueue();
+ gQueue.push(new removeElm("div"));
+ gQueue.invoke(); // SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=439566"
+ title="Include the css display property as an IAccessible2 object attribute">
+ Mozilla Bug 439566
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=460932"
+ title="text-indent and text-align should really be object attribute">
+ Mozilla Bug 460932
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=689540"
+ title="Expose IA2 margin- object attributes">
+ Mozilla Bug 689540
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=714579"
+ title="Don't use GetComputedStyle for object attribute calculation">
+ Mozilla Bug 714579
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=729831"
+ title="Don't expose CSS-based object attributes on not in tree accessible and accessible having no DOM element">
+ Mozilla Bug 729831
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="display_block" role="img"
+ style="display: block;">display: block</div>
+ <div id="display_inline" role="img"
+ style="display: inline;">display: inline</div>
+ <div id="display_inline-block" role="img"
+ style="display: inline-block;">display: inline-block</div>
+ <div id="display_list-item" role="img"
+ style="display: list-item;">display: list-item</div>
+ <div id="display_table" role="img"
+ style="display: table;">display: table</div>
+ <div id="display_inline-table" role="img"
+ style="display: inline-table;">display: inline-table</div>
+ <div id="display_table-row-group" role="img"
+ style="display: table-row-group;">display: table-row-group</div>
+ <div id="display_table-column" role="img"
+ style="display: table-column;">display: table-column</div>
+ <div id="display_table-column-group" role="img"
+ style="display: table-column-group;">display: table-column-group</div>
+ <div id="display_table-header-group" role="img"
+ style="display: table-header-group;">display: table-header-group</div>
+ <div id="display_table-footer-group" role="img"
+ style="display: table-footer-group;">display: table-footer-group</div>
+ <div id="display_table-row" role="img"
+ style="display: table-row;">display: table-row</div>
+ <div id="display_table-cell" role="img"
+ style="display: table-cell;">display: table-cell</div>
+ <div id="display_table-caption" role="img"
+ style="display: table-caption;">display: table-caption</div>
+
+ <p id="text-align_left" style="text-align: left;">text-align: left</p>
+ <p id="text-align_right" style="text-align: right;">text-align: right</p>
+ <p id="text-align_center" style="text-align: center;">text-align: center</p>
+ <p id="text-align_justify" style="text-align: justify;">text-align: justify</p>
+ <p id="text-align_inherit" style="text-align: inherit;">text-align: inherit</p>
+
+ <p id="text-indent_em" style="text-indent: 0.5em;">text-indent: 0.5em</p>
+ <p id="text-indent_ex" style="text-indent: 1ex;">text-indent: 1ex</p>
+ <p id="text-indent_in" style="text-indent: 0.5in;">text-indent: 0.5in</p>
+ <p id="text-indent_cm" style="text-indent: 2cm;">text-indent: 2cm</p>
+ <p id="text-indent_mm" style="text-indent: 10mm;">text-indent: 10mm</p>
+ <p id="text-indent_pt" style="text-indent: 30pt;">text-indent: 30pt</p>
+ <p id="text-indent_pc" style="text-indent: 2pc;">text-indent: 2pc</p>
+ <p id="text-indent_px" style="text-indent: 5px;">text-indent: 5px</p>
+ <p id="text-indent_percent" style="text-indent: 10%;">text-indent: 10%</p>
+ <p id="text-indent_inherit" style="text-indent: inherit;">text-indent: inherit</p>
+
+ <p id="margin_em" style="margin: 0.5em;">margin: 0.5em</p>
+ <p id="margin_ex" style="margin: 1ex;">margin: 1ex</p>
+ <p id="margin_in" style="margin: 0.5in;">margin: 0.5in</p>
+ <p id="margin_cm" style="margin: 2cm;">margin: 2cm</p>
+ <p id="margin_mm" style="margin: 10mm;">margin: 10mm</p>
+ <p id="margin_pt" style="margin: 30pt;">margin: 30pt</p>
+ <p id="margin_pc" style="margin: 2pc;">margin: 2pc</p>
+ <p id="margin_px" style="margin: 5px;">margin: 5px</p>
+ <p id="margin_percent" style="margin: 10%;">margin: 10%</p>
+ <p id="margin_auto" style="margin: auto;">margin: auto</p>
+ <p id="margin_inherit" style="margin: inherit;">margin: inherit</p>
+
+ <p id="margin-left" style="margin-left: 11px;">margin-left: 11px</p>
+ <p id="margin-right" style="margin-right: 21px;">margin-right</p>
+ <p id="margin-top" style="margin-top: 31px;">margin-top: 31px</p>
+ <p id="margin-bottom" style="margin-bottom: 41px;">margin-bottom: 41px</p>
+
+ <span id="span" role="group">It's span</span>
+ <div id="div">It's div</div>
+ <p id="p">It's paragraph"</p>
+ <input id="input"/>
+ <table id="table" style="margin: 2px; text-align: center; text-indent: 10%;">
+ <tr id="tr" role="group">
+ <td id="td">td</td>
+ </tr>
+ </table>
+
+ <ul>
+ <li id="listitem">item
+ </ul>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/attributes/test_obj_css.xul b/accessible/tests/mochitest/attributes/test_obj_css.xul
new file mode 100644
index 000000000..a5e93571e
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/test_obj_css.xul
@@ -0,0 +1,73 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessibility CSS-based Object Attributes Test.">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../attributes.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function doTest()
+ {
+ // CSS display
+ testCSSAttrs("display_mozbox");
+ testCSSAttrs("display_mozinlinebox");
+ testCSSAttrs("display_mozgrid");
+ testCSSAttrs("display_mozinlinegrid");
+ testCSSAttrs("display_mozgridgroup");
+ testCSSAttrs("display_mozgridline");
+ testCSSAttrs("display_mozstack");
+ testCSSAttrs("display_mozinlinestack");
+ testCSSAttrs("display_mozdeck");
+ testCSSAttrs("display_mozpopup");
+ testCSSAttrs("display_mozgroupbox");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=714579"
+ title="Don't use GetComputedStyle for object attribute calculation">
+ Mozilla Bug 714579
+ </a><br/>
+
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="display_mozbox" style="display: -moz-box;" role="img"/>
+ <vbox id="display_mozinlinebox" style="display: -moz-inline-box;" role="img"/>
+ <vbox id="display_mozgrid" style="display: -moz-grid;" role="img"/>
+ <vbox id="display_mozinlinegrid" style="display: -moz-inline-grid;" role="img"/>
+ <vbox id="display_mozgridgroup" style="display: -moz-grid-group;" role="img"/>
+ <vbox id="display_mozgridline" style="display: -moz-grid-line;" role="img"/>
+ <vbox id="display_mozstack" style="display: -moz-stack;" role="img"/>
+ <vbox id="display_mozinlinestack" style="display: -moz-inline-stack;" role="img"/>
+ <vbox id="display_mozdeck" style="display: -moz-deck;" role="img"/>
+ <vbox id="display_mozpopup" style="display: -moz-popup;" role="img"/>
+ <vbox id="display_mozgroupbox" style="display: -moz-groupbox;" role="img"/>
+
+ </hbox>
+</window>
+
diff --git a/accessible/tests/mochitest/attributes/test_obj_group.html b/accessible/tests/mochitest/attributes/test_obj_group.html
new file mode 100644
index 000000000..d5fe89471
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/test_obj_group.html
@@ -0,0 +1,469 @@
+<html>
+
+<head>
+ <title>Group attributes tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // HTML select with no size attribute.
+ testGroupAttrs("opt1-nosize", 1, 4);
+ testGroupAttrs("opt2-nosize", 2, 4);
+ testGroupAttrs("opt3-nosize", 3, 4);
+ testGroupAttrs("opt4-nosize", 4, 4);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML select
+ testGroupAttrs("opt1", 1, 2);
+ testGroupAttrs("opt2", 2, 2);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML select with options
+ // XXX bug 469123
+ //testGroupAttrs("select2_optgroup", 1, 3, 1);
+ //testGroupAttrs("select2_opt3", 2, 3, 1);
+ //testGroupAttrs("select2_opt4", 3, 3, 1);
+ //testGroupAttrs("select2_opt1", 1, 2, 2);
+ //testGroupAttrs("select2_opt2", 2, 2, 2);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML input@type="radio" within form
+ testGroupAttrs("radio1", 1, 2);
+ testGroupAttrs("radio2", 2, 2);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML input@type="radio" within document
+ testGroupAttrs("radio3", 1, 2);
+ testGroupAttrs("radio4", 2, 2);
+
+ //////////////////////////////////////////////////////////////////////////
+ // Hidden HTML input@type="radio"
+ testGroupAttrs("radio5", 1, 1);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML ul/ol
+ testGroupAttrs("li1", 1, 3);
+ testGroupAttrs("li2", 2, 3);
+ testGroupAttrs("li3", 3, 3);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML ul/ol (nested lists)
+
+ testGroupAttrs("li4", 1, 3, 1);
+ testGroupAttrs("li5", 2, 3, 1);
+ testGroupAttrs("li6", 3, 3, 1);
+
+ testGroupAttrs("n_li4", 1, 3, 2);
+ testGroupAttrs("n_li5", 2, 3, 2);
+ testGroupAttrs("n_li6", 3, 3, 2);
+
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA list
+ testGroupAttrs("li7", 1, 3);
+ testGroupAttrs("li8", 2, 3);
+ testGroupAttrs("li9", 3, 3);
+
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA list (nested lists: list -> listitem -> list -> listitem)
+ testGroupAttrs("li10", 1, 3, 1);
+ testGroupAttrs("li11", 2, 3, 1);
+ testGroupAttrs("li12", 3, 3, 1);
+
+ testGroupAttrs("n_li10", 1, 3, 2);
+ testGroupAttrs("n_li11", 2, 3, 2);
+ testGroupAttrs("n_li12", 3, 3, 2);
+
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA list (nested lists: list -> listitem -> group -> listitem)
+ testGroupAttrs("lgt_li1", 1, 2, 1);
+ testGroupAttrs("lgt_li1_nli1", 1, 2, 2);
+ testGroupAttrs("lgt_li1_nli2", 2, 2, 2);
+ testGroupAttrs("lgt_li2", 2, 2, 1);
+ testGroupAttrs("lgt_li2_nli1", 1, 2, 2);
+ testGroupAttrs("lgt_li2_nli2", 2, 2, 2);
+
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA menu (menuitem, separator, menuitemradio and menuitemcheckbox)
+ testGroupAttrs("menu_item1", 1, 2);
+ testGroupAttrs("menu_item2", 2, 2);
+ testGroupAttrs("menu_item1.1", 1, 2);
+ testGroupAttrs("menu_item1.2", 2, 2);
+ testGroupAttrs("menu_item1.3", 1, 3);
+ testGroupAttrs("menu_item1.4", 2, 3);
+ testGroupAttrs("menu_item1.5", 3, 3);
+
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA tab
+ testGroupAttrs("tab_1", 1, 3);
+ testGroupAttrs("tab_2", 2, 3);
+ testGroupAttrs("tab_3", 3, 3);
+
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA radio
+ testGroupAttrs("r1", 1, 3);
+ testGroupAttrs("r2", 2, 3);
+ testGroupAttrs("r3", 3, 3);
+
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA tree
+ testGroupAttrs("ti1", 1, 3, 1);
+ testGroupAttrs("ti2", 1, 2, 2);
+ testGroupAttrs("ti3", 2, 2, 2);
+ testGroupAttrs("ti4", 2, 3, 1);
+ testGroupAttrs("ti5", 1, 3, 2);
+ testGroupAttrs("ti6", 2, 3, 2);
+ testGroupAttrs("ti7", 3, 3, 2);
+ testGroupAttrs("ti8", 3, 3, 1);
+
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA tree (tree -> treeitem -> group -> treeitem)
+ testGroupAttrs("tree2_ti1", 1, 2, 1);
+ testGroupAttrs("tree2_ti1a", 1, 2, 2);
+ testGroupAttrs("tree2_ti1b", 2, 2, 2);
+ testGroupAttrs("tree2_ti2", 2, 2, 1);
+ testGroupAttrs("tree2_ti2a", 1, 2, 2);
+ testGroupAttrs("tree2_ti2b", 2, 2, 2);
+
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA tree (tree -> treeitem, group -> treeitem)
+ testGroupAttrs("tree3_ti1", 1, 2, 1);
+ testGroupAttrs("tree3_ti1a", 1, 2, 2);
+ testGroupAttrs("tree3_ti1b", 2, 2, 2);
+ testGroupAttrs("tree3_ti2", 2, 2, 1);
+ testGroupAttrs("tree3_ti2a", 1, 2, 2);
+ testGroupAttrs("tree3_ti2b", 2, 2, 2);
+
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA grid
+ testGroupAttrs("grid_row1", 1, 2);
+ testAbsentAttrs("grid_cell1", {"posinset":"", "setsize":""});
+ testAbsentAttrs("grid_cell2", {"posinset":"", "setsize":""});
+
+ testGroupAttrs("grid_row2", 2, 2);
+ testAbsentAttrs("grid_cell3", {"posinset":"", "setsize":""});
+ testAbsentAttrs("grid_cell4", {"posinset":"", "setsize":""});
+
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA treegrid
+ testGroupAttrs("treegrid_row1", 1, 2, 1);
+ testAbsentAttrs("treegrid_cell1", {"posinset":"", "setsize":""});
+ testAbsentAttrs("treegrid_cell2", {"posinset":"", "setsize":""});
+
+ testGroupAttrs("treegrid_row2", 1, 1, 2);
+ testAbsentAttrs("treegrid_cell3", {"posinset":"", "setsize":""});
+ testAbsentAttrs("treegrid_cell4", {"posinset":"", "setsize":""});
+
+ testGroupAttrs("treegrid_row3", 2, 2, 1);
+ testAbsentAttrs("treegrid_cell5", {"posinset":"", "setsize":""});
+ testAbsentAttrs("treegrid_cell6", {"posinset":"", "setsize":""});
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML headings
+ testGroupAttrs("h1", 0, 0, 1);
+ testGroupAttrs("h2", 0, 0, 2);
+ testGroupAttrs("h3", 0, 0, 3);
+ testGroupAttrs("h4", 0, 0, 4);
+ testGroupAttrs("h5", 0, 0, 5);
+ testGroupAttrs("h6", 0, 0, 6);
+
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA combobox
+ testGroupAttrs("combo1_opt1", 1, 4);
+ testGroupAttrs("combo1_opt2", 2, 4);
+ testGroupAttrs("combo1_opt3", 3, 4);
+ testGroupAttrs("combo1_opt4", 4, 4);
+
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA table
+ testGroupAttrs("table_cell", 3, 4);
+ testGroupAttrs("table_row", 2, 2);
+
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA list constructed by ARIA owns
+ testGroupAttrs("t1_li1", 1, 3);
+ testGroupAttrs("t1_li2", 2, 3);
+ testGroupAttrs("t1_li3", 3, 3);
+
+ // Test that group position information updates after deleting node.
+ testGroupAttrs("tree4_ti1", 1, 2, 1);
+ testGroupAttrs("tree4_ti2", 2, 2, 1);
+ var tree4element = document.getElementById("tree4_ti1");
+ var tree4acc = getAccessible("tree4");
+ tree4element.parentNode.removeChild(tree4element);
+ waitForEvent(EVENT_REORDER, tree4acc, function() {
+ testGroupAttrs("tree4_ti2", 1, 1, 1);
+ SimpleTest.finish();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=468418"
+ title="Expose level for nested lists in HTML">
+ Mozilla Bug 468418
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=844023"
+ title="group info might not be properly updated when flat trees mutate">
+ Bug 844023
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=864224"
+ title="Support nested ARIA listitems structured by role='group'">
+ Bug 864224
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=907682"
+ title=" HTML:option group position is not correct when select is collapsed">
+ Mozilla Bug 907682
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <select>
+ <option id="opt1-nosize">option1</option>
+ <option id="opt2-nosize">option2</option>
+ <option id="opt3-nosize">option3</option>
+ <option id="opt4-nosize">option4</option>
+ </select>
+
+ <select size="4">
+ <option id="opt1">option1</option>
+ <option id="opt2">option2</option>
+ </select>
+
+ <select size="4">
+ <optgroup id="select2_optgroup" label="group">
+ <option id="select2_opt1">option1</option>
+ <option id="select2_opt2">option2</option>
+ </optgroup>
+ <option id="select2_opt3">option3</option>
+ <option id="select2_opt4">option4</option>
+ </select>
+
+ <form>
+ <input type="radio" id="radio1" name="group1"/>
+ <input type="radio" id="radio2" name="group1"/>
+ </form>
+
+ <input type="radio" id="radio3" name="group2"/>
+ <input type="radio" id="radio4" name="group2"/>
+
+ <ul>
+ <li id="li1">Oranges</li>
+ <li id="li2">Apples</li>
+ <li id="li3">Bananas</li>
+ </ul>
+
+ <ol>
+ <li id="li4">Oranges</li>
+ <li id="li5">Apples</li>
+ <li id="li6">Bananas
+ <ul>
+ <li id="n_li4">Oranges</li>
+ <li id="n_li5">Apples</li>
+ <li id="n_li6">Bananas</li>
+ </ul>
+ </li>
+ </ol>
+
+ <span role="list">
+ <span role="listitem" id="li7">Oranges</span>
+ <span role="listitem" id="li8">Apples</span>
+ <span role="listitem" id="li9">Bananas</span>
+ </span>
+
+ <span role="list">
+ <span role="listitem" id="li10">Oranges</span>
+ <span role="listitem" id="li11">Apples</span>
+ <span role="listitem" id="li12">Bananas
+ <span role="list">
+ <span role="listitem" id="n_li10">Oranges</span>
+ <span role="listitem" id="n_li11">Apples</span>
+ <span role="listitem" id="n_li12">Bananas</span>
+ </span>
+ </span>
+ </span>
+
+ <div role="list">
+ <div role="listitem" id="lgt_li1">Item 1
+ <div role="group">
+ <div role="listitem" id="lgt_li1_nli1">Item 1A</div>
+ <div role="listitem" id="lgt_li1_nli2">Item 1B</div>
+ </div>
+ </div>
+ <div role="listitem" id="lgt_li2">Item 2
+ <div role="group">
+ <div role="listitem" id="lgt_li2_nli1">Item 2A</div>
+ <div role="listitem" id="lgt_li2_nli2">Item 2B</div>
+ </div>
+ </div>
+ </div>
+
+ <ul role="menubar">
+ <li role="menuitem" aria-haspopup="true" id="menu_item1">File
+ <ul role="menu">
+ <li role="menuitem" id="menu_item1.1">New</li>
+ <li role="menuitem" id="menu_item1.2">Open…</li>
+ <li role="separator">-----</li>
+ <li role="menuitem" id="menu_item1.3">Item</li>
+ <li role="menuitemradio" id="menu_item1.4">Radio</li>
+ <li role="menuitemcheckbox" id="menu_item1.5">Checkbox</li>
+ </ul>
+ </li>
+ <li role="menuitem" aria-haspopup="false" id="menu_item2">Help</li>
+ </ul>
+
+ <ul id="tablist_1" role="tablist">
+ <li id="tab_1" role="tab">Crust</li>
+ <li id="tab_2" role="tab">Veges</li>
+ <li id="tab_3" role="tab">Carnivore</li>
+ </ul>
+
+ <ul id="rg1" role="radiogroup">
+ <li id="r1" role="radio" aria-checked="false">Thai</li>
+ <li id="r2" role="radio" aria-checked="false">Subway</li>
+ <li id="r3" role="radio" aria-checked="false">Jimmy Johns</li>
+ </ul>
+
+ <table role="tree">
+ <tr role="presentation">
+ <td role="treeitem" aria-expanded="true" aria-level="1"
+ id="ti1">vegetables</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-level="2" id="ti2">cucumber</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-level="2" id="ti3">carrot</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-expanded="false" aria-level="1"
+ id="ti4">cars</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-level="2" id="ti5">mercedes</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-level="2" id="ti6">BMW</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-level="2" id="ti7">Audi</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-level="1" id="ti8">people</td>
+ </tr>
+ </table>
+
+ <ul role="tree">
+ <li role="treeitem" id="tree2_ti1">Item 1
+ <ul role="group">
+ <li role="treeitem" id="tree2_ti1a">Item 1A</li>
+ <li role="treeitem" id="tree2_ti1b">Item 1B</li>
+ </ul>
+ </li>
+ <li role="treeitem" id="tree2_ti2">Item 2
+ <ul role="group">
+ <li role="treeitem" id="tree2_ti2a">Item 2A</li>
+ <li role="treeitem" id="tree2_ti2b">Item 2B</li>
+ </ul>
+ </li>
+ </div>
+
+ <div role="tree">
+ <div role="treeitem" id="tree3_ti1">Item 1</div>
+ <div role="group">
+ <li role="treeitem" id="tree3_ti1a">Item 1A</li>
+ <li role="treeitem" id="tree3_ti1b">Item 1B</li>
+ </div>
+ <div role="treeitem" id="tree3_ti2">Item 2</div>
+ <div role="group">
+ <div role="treeitem" id="tree3_ti2a">Item 2A</div>
+ <div role="treeitem" id="tree3_ti2b">Item 2B</div>
+ </div>
+ </div>
+
+ <!-- IMPORTANT: Need to have no whitespace between elements in this tree. -->
+ <div role="tree" id="tree4"><div role="treeitem"
+ id="tree4_ti1">Item 1</div><div role="treeitem"
+ id="tree4_ti2">Item 2</div></div>
+
+ <table role="grid">
+ <tr role="row" id="grid_row1">
+ <td role="gridcell" id="grid_cell1">cell1</td>
+ <td role="gridcell" id="grid_cell2">cell2</td>
+ </tr>
+ <tr role="row" id="grid_row2">
+ <td role="gridcell" id="grid_cell3">cell3</td>
+ <td role="gridcell" id="grid_cell4">cell4</td>
+ </tr>
+ </table>
+
+ <div role="treegrid">
+ <div role="row" aria-level="1" id="treegrid_row1">
+ <div role="gridcell" id="treegrid_cell1">cell1</div>
+ <div role="gridcell" id="treegrid_cell2">cell2</div>
+ </div>
+ <div role="row" aria-level="2" id="treegrid_row2">
+ <div role="gridcell" id="treegrid_cell3">cell1</div>
+ <div role="gridcell" id="treegrid_cell4">cell2</div>
+ </div>
+ <div role="row" id="treegrid_row3">
+ <div role="gridcell" id="treegrid_cell5">cell1</div>
+ <div role="gridcell" id="treegrid_cell6">cell2</div>
+ </div>
+ </div>
+
+ <h1 id="h1">heading1</h1>
+ <h2 id="h2">heading2</h2>
+ <h3 id="h3">heading3</h3>
+ <h4 id="h4">heading4</h4>
+ <h5 id="h5">heading5</h5>
+ <h6 id="h6">heading6</h6>
+
+ <ul id="combo1" role="combobox">Password
+ <li id="combo1_opt1" role="option">Xyzzy</li>
+ <li id="combo1_opt2" role="option">Plughs</li>
+ <li id="combo1_opt3" role="option">Shazaam</li>
+ <li id="combo1_opt4" role="option">JoeSentMe</li>
+ </ul>
+
+ <form>
+ <input type="radio" style="display: none;" name="group3">
+ <input type="radio" id="radio5" name="group3">
+ </form>
+
+ <div role="table" aria-colcount="4" aria-rowcount="2">
+ <div role="row" id="table_row" aria-rowindex="2">
+ <div role="cell" id="table_cell" aria-colindex="3">cell</div>
+ </div>
+ </div>
+
+ <div role="list" aria-owns="t1_li1 t1_li2 t1_li3">
+ <div role="listitem" id="t1_li2">Apples</div>
+ <div role="listitem" id="t1_li1">Oranges</div>
+ </span>
+ <div role="listitem" id="t1_li3">Bananas</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/attributes/test_obj_group.xul b/accessible/tests/mochitest/attributes/test_obj_group.xul
new file mode 100644
index 000000000..c6943ae37
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/test_obj_group.xul
@@ -0,0 +1,216 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessibility Group Attributes ('level', 'setsize', 'posinset') Test.">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../attributes.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function openMenu(aID)
+ {
+ this.menuNode = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, this.menuNode)
+ ];
+
+ this.invoke = function openMenu_invoke()
+ {
+ this.menuNode.open = true;
+ }
+
+ this.finalCheck = function openMenu_finalCheck()
+ {
+ testGroupAttrs("menu_item1.1", 1, 1);
+ testGroupAttrs("menu_item1.2", 1, 3);
+ testGroupAttrs("menu_item1.4", 2, 3);
+ testGroupAttrs("menu_item2", 3, 3);
+ }
+
+ this.getID = function openMenu_getID()
+ {
+ return "open menu " + prettyName(aID);
+ }
+ }
+
+ function openSubMenu(aID)
+ {
+ this.menuNode = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, this.menuNode)
+ ];
+
+ this.invoke = function openSubMenu_invoke()
+ {
+ this.menuNode.open = true;
+ }
+
+ this.finalCheck = function openSubMenu_finalCheck()
+ {
+ testGroupAttrs("menu_item2.1", 1, 2, 1);
+ testGroupAttrs("menu_item2.2", 2, 2, 1);
+ }
+
+ this.getID = function openSubMenu_getID()
+ {
+ return "open submenu " + prettyName(aID);
+ }
+ }
+
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // xul:listbox (bug 417317)
+ testGroupAttrs("listitem1", 1, 4);
+ testGroupAttrs("listitem2", 2, 4);
+ testGroupAttrs("listitem3", 3, 4);
+ testGroupAttrs("listitem4", 4, 4);
+
+ //////////////////////////////////////////////////////////////////////////
+ // xul:tab
+ testGroupAttrs("tab1", 1, 2);
+ testGroupAttrs("tab2", 2, 2);
+
+ //////////////////////////////////////////////////////////////////////////
+ // xul:radio
+ testGroupAttrs("radio1", 1, 2);
+ testGroupAttrs("radio2", 2, 2);
+
+ //////////////////////////////////////////////////////////////////////////
+ // xul:menulist
+ testGroupAttrs("menulist1.1", 1);
+ testGroupAttrs("menulist1.2", 2);
+ testGroupAttrs("menulist1.3", 3);
+ testGroupAttrs("menulist1.4", 4);
+
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA menu (bug 441888)
+ testGroupAttrs("aria-menuitem", 1, 3);
+ testGroupAttrs("aria-menuitemcheckbox", 2, 3);
+ testGroupAttrs("aria-menuitemradio", 3, 3);
+ testGroupAttrs("aria-menuitem2", 1, 1);
+
+ //////////////////////////////////////////////////////////////////////////
+ // xul:menu (bug 443881)
+ gQueue = new eventQueue();
+ gQueue.push(new openMenu("menu_item1"));
+ gQueue.push(new openSubMenu("menu_item2"));
+ gQueue.invoke(); // SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=417317"
+ title="Certain types of LISTITEM accessibles no longer get attributes set like 'x of y', regression from fix for bug 389926">
+ Mozilla Bug 417317
+ </a><br/>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=443881"
+ title="take into account separators in xul menus when group attributes are calculating">
+ Mozilla Bug 443881
+ </a><br/>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=441888"
+ title="ARIA checked menu items are not included in the total list of menu items">
+ Mozilla Bug 441888
+ </a><br/>
+
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+
+ <listbox>
+ <listitem label="listitem1" id="listitem1"/>
+ <listitem label="listitem2" id="listitem2" type="checkbox"/>
+ <listitem label="listitem3" id="listitem3" type="checkbox"/>
+ <listitem label="listitem4" id="listitem4"/>
+ </listbox>
+
+ <menubar>
+ <menu label="item1" id="menu_item1">
+ <menupopup>
+ <menuitem label="item1.1" id="menu_item1.1"/>
+ <menuseparator/>
+ <menuitem label="item1.2" id="menu_item1.2"/>
+ <menuitem label="item1.3" hidden="true"/>
+ <menuitem label="item1.4" id="menu_item1.4"/>
+ <menu label="item2" id="menu_item2">
+ <menupopup>
+ <menuitem label="item2.1" id="menu_item2.1"/>
+ <menuitem label="item2.2" id="menu_item2.2"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+ </menu>
+ </menubar>
+
+ <tabbox>
+ <tabs>
+ <tab id="tab1" label="tab1"/>
+ <tab id="tab2" label="tab3"/>
+ </tabs>
+ <tabpanels>
+ <tabpanel/>
+ <tabpanel/>
+ </tabpanels>
+ </tabbox>
+
+ <radiogroup>
+ <radio id="radio1" label="radio1"/>
+ <radio id="radio2" label="radio2"/>
+ </radiogroup>
+
+ <menulist id="menulist1" label="Vehicle">
+ <menupopup>
+ <menuitem id="menulist1.1" label="Car"/>
+ <menuitem id="menulist1.2" label="Taxi"/>
+ <menuitem id="menulist1.3" label="Bus" selected="true"/>
+ <menuitem id="menulist1.4" label="Train"/>
+ </menupopup>
+ </menulist>
+
+ <vbox>
+ <description role="menuitem" id="aria-menuitem"
+ value="conventional menuitem"/>
+ <description role="menuitemcheckbox" id="aria-menuitemcheckbox"
+ value="conventional checkbox menuitem"/>
+ <description role="menuitem" hidden="true"/>
+ <description role="menuitemradio" id="aria-menuitemradio"
+ value="conventional radio menuitem"/>
+ <description role="separator"
+ value="conventional separator"/>
+ <description role="menuitem" id="aria-menuitem2"
+ value="conventional menuitem"/>
+ </vbox>
+
+ </vbox>
+ </hbox>
+</window>
+
diff --git a/accessible/tests/mochitest/attributes/test_obj_group_tree.xul b/accessible/tests/mochitest/attributes/test_obj_group_tree.xul
new file mode 100644
index 000000000..6b8461ef7
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/test_obj_group_tree.xul
@@ -0,0 +1,85 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL tree attributes tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../attributes.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function doTest()
+ {
+ var treeNode = getNode("tree");
+
+ var tree = getAccessible(treeNode);
+ var treeitem1 = tree.firstChild.nextSibling;
+ testGroupAttrs(treeitem1, 1, 4, 1);
+
+ var treeitem2 = treeitem1.nextSibling;
+ testGroupAttrs(treeitem2, 2, 4, 1);
+
+ var treeitem3 = treeitem2.nextSibling;
+ testGroupAttrs(treeitem3, 1, 2, 2);
+
+ var treeitem4 = treeitem3.nextSibling;
+ testGroupAttrs(treeitem4, 2, 2, 2);
+
+ var treeitem5 = treeitem4.nextSibling;
+ testGroupAttrs(treeitem5, 3, 4, 1);
+
+ var treeitem6 = treeitem5.nextSibling;
+ testGroupAttrs(treeitem6, 4, 4, 1);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yXULTreeLoadEvent(doTest, "tree", new nsTreeTreeView());
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727"
+ title="Reorganize implementation of XUL tree accessibility">
+ Mozilla Bug 503727
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <vbox id="debug"/>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/attributes/test_tag.html b/accessible/tests/mochitest/attributes/test_tag.html
new file mode 100644
index 000000000..e43147b24
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/test_tag.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML landmark tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest()
+ {
+ // And some AT may look for this
+ testAttrs("nav", {"tag" : "nav"}, true);
+ testAttrs("header", {"tag" : "header"}, true);
+ testAttrs("footer", {"tag" : "footer"}, true);
+ testAttrs("article", {"tag" : "article"}, true);
+ testAttrs("aside", {"tag" : "aside"}, true);
+ testAttrs("section", {"tag" : "section"}, true);
+ testAttrs("main", {"tag" : "article"}, true);
+ testAttrs("form", {"tag" : "article"}, true);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Provide mappings for html5 <nav> <header> <footer> <article>"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=593368">
+ Bug 593368
+ </a><br/>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=613502"
+ title="Map <article> like we do aria role article">
+ Bug 613502
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=610650"
+ title="Change implementation of HTML5 landmark elements to conform">
+ Bug 610650
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=614310"
+ title="Map section to pane (like role=region)">
+ Mozilla Bug 614310
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=734982"
+ title="Map ARIA role FORM">
+ Bug 734982
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <nav id="nav">a nav</nav>
+ <header id="header">a header</header>
+ <footer id="footer">a footer</footer>
+ <aside id="aside">by the way I am an aside</aside>
+ <section id="section">a section</section>
+
+ <article id="article">an article</article>
+ <article id="main" role="main">a main area</article>
+ <article id="form" role="form">a form area</article>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/attributes/test_xml-roles.html b/accessible/tests/mochitest/attributes/test_xml-roles.html
new file mode 100644
index 000000000..9b3d5aa5a
--- /dev/null
+++ b/accessible/tests/mochitest/attributes/test_xml-roles.html
@@ -0,0 +1,251 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>XML roles tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest()
+ {
+ // Some AT may look for this
+ testAttrs("nav", {"xml-roles" : "navigation"}, true);
+ testAttrs("header", {"xml-roles" : "banner"}, true);
+ testAbsentAttrs("article_header", {"xml-roles" : "banner"});
+ testAbsentAttrs("section_header", {"xml-roles" : "banner"});
+ testAttrs("footer", {"xml-roles" : "contentinfo"}, true);
+ testAbsentAttrs("article_footer", {"xml-roles" : "contentinfo"});
+ testAbsentAttrs("section_footer", {"xml-roles" : "contentinfo"});
+ testAttrs("aside", {"xml-roles" : "complementary"}, true);
+ testAttrs("section", {"xml-roles" : "region"}, true);
+ testAttrs("main", {"xml-roles" : "main"}, true); // // ARIA override
+ testAttrs("form", {"xml-roles" : "form"}, true);
+ testAttrs("feed", {"xml-roles" : "feed"}, true);
+ testAttrs("article", {"xml-roles" : "article"}, true);
+ testAttrs("main_element", {"xml-roles" : "main"}, true);
+
+ testAttrs("search", {"xml-roles" : "searchbox"}, true);
+
+ testAttrs("open-1", {"xml-roles" : "open-fence"}, true);
+ testAttrs("open-2", {"xml-roles" : "open-fence"}, true);
+ testAttrs("open-3", {"xml-roles" : "open-fence"}, true);
+ testAttrs("open-4", {"xml-roles" : "open-fence"}, true);
+ testAttrs("open-5", {"xml-roles" : "open-fence"}, true);
+ testAttrs("open-6", {"xml-roles" : "open-fence"}, true);
+ testAttrs("open-7", {"xml-roles" : "open-fence"}, true);
+
+ testAttrs("sep-1", {"xml-roles" : "separator"}, true);
+ testAttrs("sep-2", {"xml-roles" : "separator"}, true);
+ testAttrs("sep-3", {"xml-roles" : "separator"}, true);
+ testAttrs("sep-4", {"xml-roles" : "separator"}, true);
+ testAttrs("sep-5", {"xml-roles" : "separator"}, true);
+ testAttrs("sep-6", {"xml-roles" : "separator"}, true);
+ testAttrs("sep-7", {"xml-roles" : "separator"}, true);
+
+ testAttrs("close-1", {"xml-roles" : "close-fence"}, true);
+ testAttrs("close-2", {"xml-roles" : "close-fence"}, true);
+ testAttrs("close-3", {"xml-roles" : "close-fence"}, true);
+ testAttrs("close-4", {"xml-roles" : "close-fence"}, true);
+ testAttrs("close-5", {"xml-roles" : "close-fence"}, true);
+ testAttrs("close-6", {"xml-roles" : "close-fence"}, true);
+ testAttrs("close-7", {"xml-roles" : "close-fence"}, true);
+
+ testAttrs("num", {"xml-roles" : "numerator"}, true);
+ testAttrs("den", {"xml-roles" : "denominator"}, true);
+
+ testAttrs("sub-1", {"xml-roles" : "subscript"}, true);
+ testAttrs("sub-2", {"xml-roles" : "subscript"}, true);
+ testAttrs("sub-3", {"xml-roles" : "subscript"}, true);
+ testAttrs("sup-1", {"xml-roles" : "superscript"}, true);
+ testAttrs("sup-2", {"xml-roles" : "superscript"}, true);
+ testAttrs("sup-3", {"xml-roles" : "superscript"}, true);
+ testAttrs("sup-4", {"xml-roles" : "superscript"}, true);
+ testAttrs("presub-1", {"xml-roles" : "presubscript"}, true);
+ testAttrs("presub-2", {"xml-roles" : "presubscript"}, true);
+ testAttrs("presup-1", {"xml-roles" : "presuperscript"}, true);
+
+ testAttrs("under-1", {"xml-roles" : "underscript"}, true);
+ testAttrs("under-2", {"xml-roles" : "underscript"}, true);
+ testAttrs("over-1", {"xml-roles" : "overscript"}, true);
+ testAttrs("over-2", {"xml-roles" : "overscript"}, true);
+
+ testAttrs("root-index-1", {"xml-roles" : "root-index"}, true);
+
+ testAttrs("base-1", {"xml-roles" : "base"}, true);
+ testAttrs("base-2", {"xml-roles" : "base"}, true);
+ testAttrs("base-3", {"xml-roles" : "base"}, true);
+ testAttrs("base-4", {"xml-roles" : "base"}, true);
+ testAttrs("base-5", {"xml-roles" : "base"}, true);
+ testAttrs("base-6", {"xml-roles" : "base"}, true);
+ testAttrs("base-7", {"xml-roles" : "base"}, true);
+ testAttrs("base-8", {"xml-roles" : "base"}, true);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Provide mappings for html5 <nav> <header> <footer> <article>"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=593368">
+ Bug 593368
+ </a><br/>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=613502"
+ title="Map <article> like we do aria role article">
+ Bug 613502
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=610650"
+ title="Change implementation of HTML5 landmark elements to conform">
+ Bug 610650
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=614310"
+ title="Map section to pane (like role=region)">
+ Mozilla Bug 614310
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=734982"
+ title="Map ARIA role FORM">
+ Bug 734982
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=761891"
+ title="HTML5 article element should expose xml-roles:article object attribute">
+ Bug 761891
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=849624"
+ title="modify HTML5 header and footer accessibility API mapping">
+ Bug 849624
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1121518"
+ title="ARIA 1.1: Support role 'searchbox'">
+ Bug 1121518
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <nav id="nav">a nav</nav>
+ <header id="header">a header</header>
+ <footer id="footer">a footer</footer>
+ <article id="article_with_header_and_footer">
+ <header id="article_header">a header within an article</header>
+ <footer id="article_footer">a footer within an article</footer>
+ </article>
+ <section id="section_with_header_and_footer">
+ <header id="section_header">a header within an section</header>
+ <footer id="section_footer">a footer within an section</footer>
+ </section>
+ <aside id="aside">by the way I am an aside</aside>
+ <section id="section">a section</section>
+ <article id="main" role="main">a main area</article>
+ <article id="form" role="form">a form area</article>
+ <div id="feed" role="feed">a feed</div>
+ <article id="article">article</article>
+ <main id="main_element">another main area</main>
+
+ <input id="search" type="search"/>
+
+ <!-- open-fence, separator, close-fence -->
+ <math><mo id="open-1">(</mo><mi>x</mi><mo id="sep-1">,</mo><mi>y</mi><mo id="close-1">)</mo></math>
+ <math><mrow><mo id="open-2">(</mo><mi>x</mi><mo id="sep-2">,</mo><mi>y</mi><mo id="close-2">)</mo></mrow></math>
+ <math><mstyle><mo id="open-3">(</mo><mi>x</mi><mo id="sep-3">,</mo><mi>y</mi><mo id="close-3">)</mo></mstyle></math>
+ <math><msqrt><mo id="open-4">(</mo><mi>x</mi><mo id="sep-4">,</mo><mi>y</mi><mo id="close-4">)</mo></msqrt></math>
+ <math><menclose><mo id="open-5">(</mo><mi>x</mi><mo id="sep-5">,</mo><mi>y</mi><mo id="close-5">)</mo></menclose></math>
+ <math><merror><mo id="open-6">(</mo><mi>x</mi><mo id="sep-6">,</mo><mi>y</mi><mo id="close-6">)</mo></merror></math>
+ <math><mtable><mtr><mtd><mo id="open-7">(</mo><mi>x</mi><mo id="sep-7">,</mo><mi>y</mi><mo id="close-7">)</mo></mtd></mtr></mtable></math>
+
+ <!-- numerator, denominator -->
+ <math>
+ <mfrac>
+ <mi id="num">a</mi>
+ <mi id="den">b</mi>
+ </mfrac>
+ </math>
+
+ <!-- subscript, superscript, presubscript, presuperscript -->
+ <math>
+ <msub>
+ <mi id="base-1">a</mi>
+ <mi id="sub-1">b</mi>
+ </msub>
+ </math>
+ <math>
+ <msup>
+ <mi id="base-2">a</mi>
+ <mi id="sup-1">b</mi>
+ </msup>
+ </math>
+ <math>
+ <msubsup>
+ <mi id="base-3">a</mi>
+ <mi id="sub-2">b</mi>
+ <mi id="sup-2">c</mi>
+ </msubsup>
+ </math>
+ <math>
+ <mmultiscripts>
+ <mi id="base-4">a</mi>
+ <mi id="sub-3">b</mi>
+ <mi id="sup-3">c</mi>
+ <none/>
+ <mi id="sup-4">d</mi>
+ <mprescripts/>
+ <mi id="presub-1">e</mi>
+ <none/>
+ <mi id="presub-2">f</mi>
+ <mi id="presup-1">g</mi>
+ </mmultiscripts>
+ </math>
+
+ <!-- underscript, overscript -->
+ <math>
+ <munder>
+ <mi id="base-5">a</mi>
+ <mi id="under-1">b</mi>
+ </munder>
+ </math>
+ <math>
+ <mover>
+ <mi id="base-6">a</mi>
+ <mi id="over-1">b</mi>
+ </mover>
+ </math>
+ <math>
+ <munderover>
+ <mi id="base-7">a</mi>
+ <mi id="under-2">b</mi>
+ <mi id="over-2">c</mi>
+ </munderover>
+ </math>
+
+ <!-- root-index -->
+ <math>
+ <mroot>
+ <mi id="base-8">a</mi>
+ <mi id="root-index-1">b</mi>
+ </mroot>
+ </math>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/autocomplete.js b/accessible/tests/mochitest/autocomplete.js
new file mode 100644
index 000000000..4de881b4d
--- /dev/null
+++ b/accessible/tests/mochitest/autocomplete.js
@@ -0,0 +1,221 @@
+
+const nsISupports = Components.interfaces.nsISupports;
+const nsIAutoCompleteResult = Components.interfaces.nsIAutoCompleteResult;
+const nsIAutoCompleteSearch = Components.interfaces.nsIAutoCompleteSearch;
+const nsIFactory = Components.interfaces.nsIFactory;
+const nsIUUIDGenerator = Components.interfaces.nsIUUIDGenerator;
+const nsIComponentRegistrar = Components.interfaces.nsIComponentRegistrar;
+
+var gDefaultAutoCompleteSearch = null;
+
+/**
+ * Register 'test-a11y-search' AutoCompleteSearch.
+ *
+ * @param aValues [in] set of possible results values
+ * @param aComments [in] set of possible results descriptions
+ */
+function initAutoComplete(aValues, aComments)
+{
+ var allResults = new ResultsHeap(aValues, aComments);
+ gDefaultAutoCompleteSearch =
+ new AutoCompleteSearch("test-a11y-search", allResults);
+ registerAutoCompleteSearch(gDefaultAutoCompleteSearch,
+ "Accessibility Test AutoCompleteSearch");
+}
+
+/**
+ * Unregister 'test-a11y-search' AutoCompleteSearch.
+ */
+function shutdownAutoComplete()
+{
+ unregisterAutoCompleteSearch(gDefaultAutoCompleteSearch);
+ gDefaultAutoCompleteSearch.cid = null;
+ gDefaultAutoCompleteSearch = null;
+}
+
+
+/**
+ * Register the given AutoCompleteSearch.
+ *
+ * @param aSearch [in] AutoCompleteSearch object
+ * @param aDescription [in] description of the search object
+ */
+function registerAutoCompleteSearch(aSearch, aDescription)
+{
+ var name = "@mozilla.org/autocomplete/search;1?name=" + aSearch.name;
+
+ var uuidGenerator = Components.classes["@mozilla.org/uuid-generator;1"].
+ getService(nsIUUIDGenerator);
+ var cid = uuidGenerator.generateUUID();
+
+ var componentManager = Components.manager.QueryInterface(nsIComponentRegistrar);
+ componentManager.registerFactory(cid, aDescription, name, aSearch);
+
+ // Keep the id on the object so we can unregister later.
+ aSearch.cid = cid;
+}
+
+/**
+ * Unregister the given AutoCompleteSearch.
+ */
+function unregisterAutoCompleteSearch(aSearch)
+{
+ var componentManager = Components.manager.QueryInterface(nsIComponentRegistrar);
+ componentManager.unregisterFactory(aSearch.cid, aSearch);
+}
+
+
+/**
+ * A container to keep all possible results of autocomplete search.
+ */
+function ResultsHeap(aValues, aComments)
+{
+ this.values = aValues;
+ this.comments = aComments;
+}
+
+ResultsHeap.prototype =
+{
+ constructor: ResultsHeap,
+
+ /**
+ * Return AutoCompleteResult for the given search string.
+ */
+ getAutoCompleteResultFor: function(aSearchString)
+ {
+ var values = [], comments = [];
+ for (var idx = 0; idx < this.values.length; idx++) {
+ if (this.values[idx].indexOf(aSearchString) != -1) {
+ values.push(this.values[idx]);
+ comments.push(this.comments[idx]);
+ }
+ }
+ return new AutoCompleteResult(values, comments);
+ }
+}
+
+
+/**
+ * nsIAutoCompleteSearch implementation.
+ *
+ * @param aName [in] the name of autocomplete search
+ * @param aAllResults [in] ResultsHeap object
+ */
+function AutoCompleteSearch(aName, aAllResults)
+{
+ this.name = aName;
+ this.allResults = aAllResults;
+}
+
+AutoCompleteSearch.prototype =
+{
+ constructor: AutoCompleteSearch,
+
+ // nsIAutoCompleteSearch implementation
+ startSearch: function(aSearchString, aSearchParam, aPreviousResult,
+ aListener)
+ {
+ var result = this.allResults.getAutoCompleteResultFor(aSearchString);
+ aListener.onSearchResult(this, result);
+ },
+
+ stopSearch: function() {},
+
+ // nsISupports implementation
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(nsISupports) ||
+ iid.equals(nsIFactory) ||
+ iid.equals(nsIAutoCompleteSearch))
+ return this;
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ // nsIFactory implementation
+ createInstance: function(outer, iid)
+ {
+ return this.QueryInterface(iid);
+ },
+
+ // Search name. Used by AutoCompleteController.
+ name: null,
+
+ // Results heap.
+ allResults: null
+}
+
+
+/**
+ * nsIAutoCompleteResult implementation.
+ */
+function AutoCompleteResult(aValues, aComments)
+{
+ this.values = aValues;
+ this.comments = aComments;
+
+ if (this.values.length > 0)
+ this.searchResult = nsIAutoCompleteResult.RESULT_SUCCESS;
+ else
+ this.searchResult = nsIAutoCompleteResult.NOMATCH;
+}
+
+AutoCompleteResult.prototype =
+{
+ constructor: AutoCompleteResult,
+
+ searchString: "",
+ searchResult: null,
+
+ defaultIndex: 0,
+
+ get matchCount()
+ {
+ return this.values.length;
+ },
+
+ getValueAt: function(aIndex)
+ {
+ return this.values[aIndex];
+ },
+
+ getLabelAt: function(aIndex)
+ {
+ return this.getValueAt(aIndex);
+ },
+
+ getCommentAt: function(aIndex)
+ {
+ return this.comments[aIndex];
+ },
+
+ getStyleAt: function(aIndex)
+ {
+ return null;
+ },
+
+ getImageAt: function(aIndex)
+ {
+ return "";
+ },
+
+ getFinalCompleteValueAt: function(aIndex)
+ {
+ return this.getValueAt(aIndex);
+ },
+
+ removeValueAt: function (aRowIndex, aRemoveFromDb) {},
+
+ // nsISupports implementation
+ QueryInterface: function(iid) {
+ if (iid.equals(nsISupports) ||
+ iid.equals(nsIAutoCompleteResult))
+ return this;
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ // Data
+ values: null,
+ comments: null
+}
diff --git a/accessible/tests/mochitest/bounds/a11y.ini b/accessible/tests/mochitest/bounds/a11y.ini
new file mode 100644
index 000000000..d60bd46a5
--- /dev/null
+++ b/accessible/tests/mochitest/bounds/a11y.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+
+[test_list.html]
+[test_select.html]
+[test_zoom.html]
+[test_zoom_text.html]
diff --git a/accessible/tests/mochitest/bounds/test_list.html b/accessible/tests/mochitest/bounds/test_list.html
new file mode 100644
index 000000000..38a79689b
--- /dev/null
+++ b/accessible/tests/mochitest/bounds/test_list.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Accessible boundaries when page is zoomed</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../layout.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ // Inside list
+ var li = getAccessible("insidelist_item");
+ testBounds(li);
+
+ var [xLI, yLI, widthLI, heightLI] = getBounds(li);
+ var bullet = li.firstChild;
+ var [x, y, width, height] = getBounds(bullet);
+ is(x, xLI,
+ "Bullet x should match to list item x");
+ ok(y >= yLI,
+ "Bullet y= " + y + " should be not less than list item y=" + yLI);
+ ok(width < widthLI,
+ "Bullet width should be lesser list item width");
+ ok(height <= heightLI,
+ "Bullet height= " + height + " should be not greater than list item height=" + heightLI);
+
+ // Outside list
+ li = getAccessible("outsidelist_item");
+ var [xLIElm, yLIElm, widthLIElm, heightLIElm] = getBoundsForDOMElm(li);
+ [xLI, yLI, widthLI, heightLI] = getBounds(li);
+
+ ok(xLI < xLIElm,
+ "Outside list item x=" + xLI + " should be lesser than list item element x=" + xLIElm);
+ is(yLI, yLIElm,
+ "Outside list item y should match to list item element y");
+ ok(widthLI > widthLIElm,
+ "Outside list item width=" + widthLI + " should be greater than list item element width=" + widthLIElm);
+ is(heightLI, heightLIElm,
+ "Outside list item height should match to list item element height");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=754627"
+ title="GetBounds on bullet return wrong values">
+ Mozilla Bug 754627
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <ul style="list-style-position: inside;">
+ <li id="insidelist_item">item</li>
+ </ul>
+
+ <ul style="list-style-position: outside;">
+ <li id="outsidelist_item">item</li>
+ </ul>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/bounds/test_select.html b/accessible/tests/mochitest/bounds/test_select.html
new file mode 100644
index 000000000..395dad1b6
--- /dev/null
+++ b/accessible/tests/mochitest/bounds/test_select.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Accessible boundaries when page is zoomed</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../layout.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function openComboboxNCheckBounds(aID)
+ {
+ this.combobox = getAccessible(aID);
+ this.comboboxList = this.combobox.firstChild;
+ this.comboboxOption = this.comboboxList.firstChild;
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, this.comboboxOption)
+ ];
+
+ this.invoke = function openComboboxNCheckBounds_invoke()
+ {
+ getNode(aID).focus();
+ synthesizeKey("VK_DOWN", { altKey: true });
+ }
+
+ this.finalCheck = function openComboboxNCheckBounds_invoke()
+ {
+ testBounds(this.comboboxOption);
+ }
+
+ this.getID = function openComboboxNCheckBounds_getID()
+ {
+ return "open combobox and test boundaries";
+ }
+ }
+
+ //gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ // Combobox
+ testBounds("combobox");
+
+ // Option boundaries matches to combobox boundaries when collapsed.
+ var selectBounds = getBoundsForDOMElm("combobox");
+ testBounds("option1", selectBounds);
+
+ // Open combobox and test option boundaries.
+ gQueue = new eventQueue();
+ gQueue.push(new openComboboxNCheckBounds("combobox"));
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <select id="combobox">
+ <option id="option1">item1</option>
+ <option>item2</option>
+ </select>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/bounds/test_zoom.html b/accessible/tests/mochitest/bounds/test_zoom.html
new file mode 100644
index 000000000..fc2dee482
--- /dev/null
+++ b/accessible/tests/mochitest/bounds/test_zoom.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Accessible boundaries when page is zoomed</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../layout.js"></script>
+ <script type="application/javascript"
+ src="../browser.js"></script>
+
+ <script type="application/javascript">
+ //gA11yEventDumpToConsole = true;
+ //enableLogging("tree,verbose");
+ function doPreTest()
+ {
+ var tabDocument = currentTabDocument();
+ var imgMap = tabDocument.getElementById("imgmap");
+ waitForImageMap(imgMap, doTest);
+ }
+
+ function doTest()
+ {
+ // Bug 746176: Failure of this whole test file on OS X.
+ if (MAC) {
+ todo(false, "Fix bug 746176 on Mac");
+ closeBrowserWindow();
+ SimpleTest.finish();
+ return;
+ }
+
+ var tabDocument = currentTabDocument();
+ var p1 = tabDocument.getElementById("p1");
+ var p2 = tabDocument.getElementById("p2");
+
+ var imgMap = tabDocument.getElementById("imgmap");
+ var imgMapAcc = getAccessible(imgMap);
+ var area = imgMapAcc.firstChild;
+
+ testBounds(p1);
+ testBounds(p2);
+ testBounds(area);
+
+ zoomDocument(tabDocument, 2.0);
+
+ testBounds(p1);
+ testBounds(p2);
+ testBounds(area);
+
+ closeBrowserWindow();
+ SimpleTest.finish();
+ }
+
+ var url = "data:text/html,<html><body>";
+ url += "<p id='p1'>para 1</p><p id='p2'>para 2</p>";
+ url += "<map name='atoz_map' id='map'>";
+ url += " <area id='area1' href='http%3A%2F%2Fmozilla.org'";
+ url += " coords=17,0,30,14' alt='mozilla.org' shape='rect'>";
+ url += "</map>";
+ url += "<img id='imgmap' width='447' height='15'";
+ url += " usemap='%23atoz_map'";
+ url += " src='chrome%3A%2F%2Fmochitests%2Fcontent%2Fa11y%2Faccessible%2Fletters.gif'>";
+ url += "</body></html>";
+
+ SimpleTest.waitForExplicitFinish();
+ openBrowserWindow(doPreTest,
+ url,
+ { left: 0, top: 0, width: 600, height: 600 });
+ </script>
+
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=650241"
+ title="Location returned by accessibles incorrect when page zoomed">
+ Mozilla Bug 650241
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/bounds/test_zoom_text.html b/accessible/tests/mochitest/bounds/test_zoom_text.html
new file mode 100644
index 000000000..1637f344e
--- /dev/null
+++ b/accessible/tests/mochitest/bounds/test_zoom_text.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>The text range boundary when page is zoomed</title>
+ <meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../layout.js"></script>
+ <script type="application/javascript"
+ src="../browser.js"></script>
+
+ <script type="application/javascript">
+ function testTextNode(aDoc, aContainerID)
+ {
+ var hyperTextNode = aDoc.getElementById(aContainerID);
+ var textNode = hyperTextNode.firstChild;
+
+ var [x, y, width, height] = getBounds(textNode);
+ testTextBounds(hyperTextNode, 0, -1, [x, y, width, height],
+ COORDTYPE_SCREEN_RELATIVE);
+ }
+
+ function doTest()
+ {
+ var tabDocument = currentTabDocument();
+ testTextNode(tabDocument, "p1");
+ testTextNode(tabDocument, "p2");
+
+ zoomDocument(tabDocument, 2.0);
+
+ testTextNode(tabDocument, "p1");
+
+ zoomDocument(tabDocument, 1.0);
+
+ closeBrowserWindow();
+ SimpleTest.finish();
+ }
+
+ var url = "data:text/html,<html>" +
+ "<meta http-equiv='Content-Type' content='text/html;charset=utf-8'>" +
+ "</meta><body>" +
+ "<p id='p1' style='font-family: monospace;'>Tilimilitryamdiya</p>" +
+ "<p id='p2'>ل</p>"
+ "</body></html>";
+
+ SimpleTest.waitForExplicitFinish();
+ openBrowserWindow(doTest,
+ url,
+ { left: 0, top: 0, width: 600, height: 600 });
+
+ </script>
+
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=727942"
+ title="Text range boundaries are incorrect when page is zoomed">
+ Mozilla Bug 727942
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/browser.js b/accessible/tests/mochitest/browser.js
new file mode 100644
index 000000000..f84f545d8
--- /dev/null
+++ b/accessible/tests/mochitest/browser.js
@@ -0,0 +1,153 @@
+/**
+ * Load the browser with the given url and then invokes the given function.
+ */
+function openBrowserWindow(aFunc, aURL, aRect)
+{
+ gBrowserContext.testFunc = aFunc;
+ gBrowserContext.startURL = aURL;
+ gBrowserContext.browserRect = aRect;
+
+ addLoadEvent(openBrowserWindowIntl);
+}
+
+/**
+ * Close the browser window.
+ */
+function closeBrowserWindow()
+{
+ gBrowserContext.browserWnd.close();
+}
+
+/**
+ * Return the browser window object.
+ */
+function browserWindow()
+{
+ return gBrowserContext.browserWnd;
+}
+
+/**
+ * Return the document of the browser window.
+ */
+function browserDocument()
+{
+ return browserWindow().document;
+}
+
+/**
+ * Return tab browser object.
+ */
+function tabBrowser()
+{
+ return browserWindow().gBrowser;
+}
+
+/**
+ * Return browser element of the current tab.
+ */
+function currentBrowser()
+{
+ return tabBrowser().selectedBrowser;
+}
+
+/**
+ * Return DOM document of the current tab.
+ */
+function currentTabDocument()
+{
+ return currentBrowser().contentDocument;
+}
+
+/**
+ * Return window of the current tab.
+ */
+function currentTabWindow()
+{
+ return currentTabDocument().defaultView;
+}
+
+/**
+ * Return browser element of the tab at the given index.
+ */
+function browserAt(aIndex)
+{
+ return tabBrowser().getBrowserAtIndex(aIndex);
+}
+
+/**
+ * Return DOM document of the tab at the given index.
+ */
+function tabDocumentAt(aIndex)
+{
+ return browserAt(aIndex).contentDocument;
+}
+
+/**
+ * Return input element of address bar.
+ */
+function urlbarInput()
+{
+ return browserWindow().document.getElementById("urlbar").inputField;
+}
+
+/**
+ * Return reload button.
+ */
+function reloadButton()
+{
+ return browserWindow().document.getElementById("urlbar-reload-button");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// private section
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gBrowserContext =
+{
+ browserWnd: null,
+ testFunc: null,
+ startURL: ""
+};
+
+function openBrowserWindowIntl()
+{
+ var params = "chrome,all,dialog=no";
+ var rect = gBrowserContext.browserRect;
+ if (rect) {
+ if ("left" in rect)
+ params += ",left=" + rect.left;
+ if ("top" in rect)
+ params += ",top=" + rect.top;
+ if ("width" in rect)
+ params += ",width=" + rect.width;
+ if ("height" in rect)
+ params += ",height=" + rect.height;
+ }
+
+ gBrowserContext.browserWnd =
+ window.openDialog(Services.prefs.getCharPref("browser.chromeURL"),
+ "_blank", params,
+ gBrowserContext.startURL);
+
+ whenDelayedStartupFinished(browserWindow(), function () {
+ addA11yLoadEvent(startBrowserTests, browserWindow());
+ });
+}
+
+function startBrowserTests()
+{
+ if (gBrowserContext.startURL) // wait for load
+ addA11yLoadEvent(gBrowserContext.testFunc, currentBrowser().contentWindow);
+ else
+ gBrowserContext.testFunc();
+}
+
+function whenDelayedStartupFinished(aWindow, aCallback) {
+ Services.obs.addObserver(function observer(aSubject, aTopic) {
+ if (aWindow == aSubject) {
+ Services.obs.removeObserver(observer, aTopic);
+ setTimeout(aCallback, 0);
+ }
+ }, "browser-delayed-startup-finished", false);
+}
diff --git a/accessible/tests/mochitest/common.js b/accessible/tests/mochitest/common.js
new file mode 100644
index 000000000..1e48fa067
--- /dev/null
+++ b/accessible/tests/mochitest/common.js
@@ -0,0 +1,952 @@
+////////////////////////////////////////////////////////////////////////////////
+// Interfaces
+
+const nsIAccessibilityService = Components.interfaces.nsIAccessibilityService;
+
+const nsIAccessibleEvent = Components.interfaces.nsIAccessibleEvent;
+const nsIAccessibleStateChangeEvent =
+ Components.interfaces.nsIAccessibleStateChangeEvent;
+const nsIAccessibleCaretMoveEvent =
+ Components.interfaces.nsIAccessibleCaretMoveEvent;
+const nsIAccessibleTextChangeEvent =
+ Components.interfaces.nsIAccessibleTextChangeEvent;
+const nsIAccessibleVirtualCursorChangeEvent =
+ Components.interfaces.nsIAccessibleVirtualCursorChangeEvent;
+const nsIAccessibleObjectAttributeChangedEvent =
+ Components.interfaces.nsIAccessibleObjectAttributeChangedEvent;
+
+const nsIAccessibleStates = Components.interfaces.nsIAccessibleStates;
+const nsIAccessibleRole = Components.interfaces.nsIAccessibleRole;
+const nsIAccessibleScrollType = Components.interfaces.nsIAccessibleScrollType;
+const nsIAccessibleCoordinateType = Components.interfaces.nsIAccessibleCoordinateType;
+
+const nsIAccessibleRelation = Components.interfaces.nsIAccessibleRelation;
+const nsIAccessibleTextRange = Components.interfaces.nsIAccessibleTextRange;
+
+const nsIAccessible = Components.interfaces.nsIAccessible;
+
+const nsIAccessibleDocument = Components.interfaces.nsIAccessibleDocument;
+const nsIAccessibleApplication = Components.interfaces.nsIAccessibleApplication;
+
+const nsIAccessibleText = Components.interfaces.nsIAccessibleText;
+const nsIAccessibleEditableText = Components.interfaces.nsIAccessibleEditableText;
+
+const nsIAccessibleHyperLink = Components.interfaces.nsIAccessibleHyperLink;
+const nsIAccessibleHyperText = Components.interfaces.nsIAccessibleHyperText;
+
+const nsIAccessibleImage = Components.interfaces.nsIAccessibleImage;
+const nsIAccessiblePivot = Components.interfaces.nsIAccessiblePivot;
+const nsIAccessibleSelectable = Components.interfaces.nsIAccessibleSelectable;
+const nsIAccessibleTable = Components.interfaces.nsIAccessibleTable;
+const nsIAccessibleTableCell = Components.interfaces.nsIAccessibleTableCell;
+const nsIAccessibleTraversalRule = Components.interfaces.nsIAccessibleTraversalRule;
+const nsIAccessibleValue = Components.interfaces.nsIAccessibleValue;
+
+const nsIObserverService = Components.interfaces.nsIObserverService;
+
+const nsIDOMDocument = Components.interfaces.nsIDOMDocument;
+const nsIDOMEvent = Components.interfaces.nsIDOMEvent;
+const nsIDOMHTMLDocument = Components.interfaces.nsIDOMHTMLDocument;
+const nsIDOMNode = Components.interfaces.nsIDOMNode;
+const nsIDOMHTMLElement = Components.interfaces.nsIDOMHTMLElement;
+const nsIDOMWindow = Components.interfaces.nsIDOMWindow;
+const nsIDOMXULElement = Components.interfaces.nsIDOMXULElement;
+
+const nsIPropertyElement = Components.interfaces.nsIPropertyElement;
+
+////////////////////////////////////////////////////////////////////////////////
+// OS detect
+
+const MAC = (navigator.platform.indexOf("Mac") != -1);
+const LINUX = (navigator.platform.indexOf("Linux") != -1);
+const SOLARIS = (navigator.platform.indexOf("SunOS") != -1);
+const WIN = (navigator.platform.indexOf("Win") != -1);
+
+////////////////////////////////////////////////////////////////////////////////
+// Application detect
+// Firefox is assumed by default.
+
+const SEAMONKEY = navigator.userAgent.match(/ SeaMonkey\//);
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible general
+
+const STATE_BUSY = nsIAccessibleStates.STATE_BUSY;
+
+const SCROLL_TYPE_ANYWHERE = nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE;
+
+const COORDTYPE_SCREEN_RELATIVE = nsIAccessibleCoordinateType.COORDTYPE_SCREEN_RELATIVE;
+const COORDTYPE_WINDOW_RELATIVE = nsIAccessibleCoordinateType.COORDTYPE_WINDOW_RELATIVE;
+const COORDTYPE_PARENT_RELATIVE = nsIAccessibleCoordinateType.COORDTYPE_PARENT_RELATIVE;
+
+const kEmbedChar = String.fromCharCode(0xfffc);
+
+const kDiscBulletChar = String.fromCharCode(0x2022);
+const kDiscBulletText = kDiscBulletChar + " ";
+const kCircleBulletText = String.fromCharCode(0x25e6) + " ";
+const kSquareBulletText = String.fromCharCode(0x25fe) + " ";
+
+const MAX_TRIM_LENGTH = 100;
+
+/**
+ * Services to determine if e10s is enabled.
+ */
+Components.utils.import('resource://gre/modules/Services.jsm');
+
+/**
+ * nsIAccessibilityService service.
+ */
+var gAccService = Components.classes["@mozilla.org/accessibilityService;1"].
+ getService(nsIAccessibilityService);
+
+/**
+ * Enable/disable logging.
+ */
+function enableLogging(aModules)
+{
+ gAccService.setLogging(aModules);
+}
+function disableLogging()
+{
+ gAccService.setLogging("");
+}
+function isLogged(aModule)
+{
+ return gAccService.isLogged(aModule);
+}
+
+/**
+ * Dumps the accessible tree into console.
+ */
+function dumpTree(aId, aMsg)
+{
+ function dumpTreeIntl(acc, indent)
+ {
+ dump(indent + prettyName(acc) + "\n");
+
+ var children = acc.children;
+ for (var i = 0; i < children.length; i++) {
+ var child = children.queryElementAt(i, nsIAccessible);
+ dumpTreeIntl(child, indent + " ");
+ }
+ }
+
+ function dumpDOMTreeIntl(node, indent)
+ {
+ dump(indent + prettyName(node) + "\n");
+
+ var children = node.childNodes;
+ for (var i = 0; i < children.length; i++) {
+ var child = children.item(i);
+ dumpDOMTreeIntl(child, indent + " ");
+ }
+ }
+
+ dump(aMsg + "\n");
+ var root = getAccessible(aId);
+ dumpTreeIntl(root, " ");
+
+ dump("DOM tree:\n");
+ dumpDOMTreeIntl(getNode(aId), " ");
+}
+
+/**
+ * Invokes the given function when document is loaded and focused. Preferable
+ * to mochitests 'addLoadEvent' function -- additionally ensures state of the
+ * document accessible is not busy.
+ *
+ * @param aFunc the function to invoke
+ */
+function addA11yLoadEvent(aFunc, aWindow)
+{
+ function waitForDocLoad()
+ {
+ window.setTimeout(
+ function()
+ {
+ var targetDocument = aWindow ? aWindow.document : document;
+ var accDoc = getAccessible(targetDocument);
+ var state = {};
+ accDoc.getState(state, {});
+ if (state.value & STATE_BUSY)
+ return waitForDocLoad();
+
+ window.setTimeout(aFunc, 0);
+ },
+ 0
+ );
+ }
+
+ SimpleTest.waitForFocus(waitForDocLoad, aWindow);
+}
+
+/**
+ * Analogy of SimpleTest.is function used to compare objects.
+ */
+function isObject(aObj, aExpectedObj, aMsg)
+{
+ if (aObj == aExpectedObj) {
+ ok(true, aMsg);
+ return;
+ }
+
+ ok(false,
+ aMsg + " - got '" + prettyName(aObj) +
+ "', expected '" + prettyName(aExpectedObj) + "'");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Helpers for getting DOM node/accessible
+
+/**
+ * Return the DOM node by identifier (may be accessible, DOM node or ID).
+ */
+function getNode(aAccOrNodeOrID, aDocument)
+{
+ if (!aAccOrNodeOrID)
+ return null;
+
+ if (aAccOrNodeOrID instanceof nsIDOMNode)
+ return aAccOrNodeOrID;
+
+ if (aAccOrNodeOrID instanceof nsIAccessible)
+ return aAccOrNodeOrID.DOMNode;
+
+ var node = (aDocument || document).getElementById(aAccOrNodeOrID);
+ if (!node) {
+ ok(false, "Can't get DOM element for " + aAccOrNodeOrID);
+ return null;
+ }
+
+ return node;
+}
+
+/**
+ * Constants indicates getAccessible doesn't fail if there is no accessible.
+ */
+const DONOTFAIL_IF_NO_ACC = 1;
+
+/**
+ * Constants indicates getAccessible won't fail if accessible doesn't implement
+ * the requested interfaces.
+ */
+const DONOTFAIL_IF_NO_INTERFACE = 2;
+
+/**
+ * Return accessible for the given identifier (may be ID attribute or DOM
+ * element or accessible object) or null.
+ *
+ * @param aAccOrElmOrID [in] identifier to get an accessible implementing
+ * the given interfaces
+ * @param aInterfaces [in, optional] the interface or an array interfaces
+ * to query it/them from obtained accessible
+ * @param aElmObj [out, optional] object to store DOM element which
+ * accessible is obtained for
+ * @param aDoNotFailIf [in, optional] no error for special cases (see
+ * constants above)
+ */
+function getAccessible(aAccOrElmOrID, aInterfaces, aElmObj, aDoNotFailIf)
+{
+ if (!aAccOrElmOrID)
+ return null;
+
+ var elm = null;
+
+ if (aAccOrElmOrID instanceof nsIAccessible) {
+ try { elm = aAccOrElmOrID.DOMNode; } catch(e) { }
+
+ } else if (aAccOrElmOrID instanceof nsIDOMNode) {
+ elm = aAccOrElmOrID;
+
+ } else {
+ elm = document.getElementById(aAccOrElmOrID);
+ if (!elm) {
+ ok(false, "Can't get DOM element for " + aAccOrElmOrID);
+ return null;
+ }
+ }
+
+ if (aElmObj && (typeof aElmObj == "object"))
+ aElmObj.value = elm;
+
+ var acc = (aAccOrElmOrID instanceof nsIAccessible) ? aAccOrElmOrID : null;
+ if (!acc) {
+ try {
+ acc = gAccService.getAccessibleFor(elm);
+ } catch (e) {
+ }
+
+ if (!acc) {
+ if (!(aDoNotFailIf & DONOTFAIL_IF_NO_ACC))
+ ok(false, "Can't get accessible for " + prettyName(aAccOrElmOrID));
+
+ return null;
+ }
+ }
+
+ if (!aInterfaces)
+ return acc;
+
+ if (!(aInterfaces instanceof Array))
+ aInterfaces = [ aInterfaces ];
+
+ for (var index = 0; index < aInterfaces.length; index++) {
+ if (acc instanceof aInterfaces[index]) {
+ continue;
+ }
+ try {
+ acc.QueryInterface(aInterfaces[index]);
+ } catch (e) {
+ if (!(aDoNotFailIf & DONOTFAIL_IF_NO_INTERFACE))
+ ok(false, "Can't query " + aInterfaces[index] + " for " + aAccOrElmOrID);
+
+ return null;
+ }
+ }
+
+ return acc;
+}
+
+/**
+ * Return true if the given identifier has an accessible, or exposes the wanted
+ * interfaces.
+ */
+function isAccessible(aAccOrElmOrID, aInterfaces)
+{
+ return getAccessible(aAccOrElmOrID, aInterfaces, null,
+ DONOTFAIL_IF_NO_ACC | DONOTFAIL_IF_NO_INTERFACE) ?
+ true : false;
+}
+
+/**
+ * Return an accessible that contains the DOM node for the given identifier.
+ */
+function getContainerAccessible(aAccOrElmOrID)
+{
+ var node = getNode(aAccOrElmOrID);
+ if (!node)
+ return null;
+
+ while ((node = node.parentNode) && !isAccessible(node));
+ return node ? getAccessible(node) : null;
+}
+
+/**
+ * Return root accessible for the given identifier.
+ */
+function getRootAccessible(aAccOrElmOrID)
+{
+ var acc = getAccessible(aAccOrElmOrID ? aAccOrElmOrID : document);
+ return acc ? acc.rootDocument.QueryInterface(nsIAccessible) : null;
+}
+
+/**
+ * Return tab document accessible the given accessible is contained by.
+ */
+function getTabDocAccessible(aAccOrElmOrID)
+{
+ var acc = getAccessible(aAccOrElmOrID ? aAccOrElmOrID : document);
+
+ var docAcc = acc.document.QueryInterface(nsIAccessible);
+ var containerDocAcc = docAcc.parent.document;
+
+ // Test is running is stand-alone mode.
+ if (acc.rootDocument == containerDocAcc)
+ return docAcc;
+
+ // In the case of running all tests together.
+ return containerDocAcc.QueryInterface(nsIAccessible);
+}
+
+/**
+ * Return application accessible.
+ */
+function getApplicationAccessible()
+{
+ return gAccService.getApplicationAccessible().
+ QueryInterface(nsIAccessibleApplication);
+}
+
+/**
+ * A version of accessible tree testing, doesn't fail if tree is not complete.
+ */
+function testElm(aID, aTreeObj)
+{
+ testAccessibleTree(aID, aTreeObj, kSkipTreeFullCheck);
+}
+
+/**
+ * Flags used for testAccessibleTree
+ */
+const kSkipTreeFullCheck = 1;
+
+/**
+ * Compare expected and actual accessibles trees.
+ *
+ * @param aAccOrElmOrID [in] accessible identifier
+ * @param aAccTree [in] JS object, each field corresponds to property of
+ * accessible object. Additionally special properties
+ * are presented:
+ * children - an array of JS objects representing
+ * children of accessible
+ * states - an object having states and extraStates
+ * fields
+ * @param aFlags [in, optional] flags, see constants above
+ */
+function testAccessibleTree(aAccOrElmOrID, aAccTree, aFlags)
+{
+ var acc = getAccessible(aAccOrElmOrID);
+ if (!acc)
+ return;
+
+ var accTree = aAccTree;
+
+ // Support of simplified accessible tree object.
+ accTree = normalizeAccTreeObj(accTree);
+
+ // Test accessible properties.
+ for (var prop in accTree) {
+ var msg = "Wrong value of property '" + prop + "' for " +
+ prettyName(acc) + ".";
+
+ switch (prop) {
+ case "actions": {
+ testActionNames(acc, accTree.actions);
+ break;
+ }
+
+ case "attributes":
+ testAttrs(acc, accTree[prop], true);
+ break;
+
+ case "absentAttributes":
+ testAbsentAttrs(acc, accTree[prop]);
+ break;
+
+ case "interfaces": {
+ var ifaces = (accTree[prop] instanceof Array) ?
+ accTree[prop] : [ accTree[prop] ];
+ for (var i = 0; i < ifaces.length; i++) {
+ ok((acc instanceof ifaces[i]),
+ "No " + ifaces[i] + " interface on " + prettyName(acc));
+ }
+ break;
+ }
+
+ case "relations": {
+ for (var rel in accTree[prop])
+ testRelation(acc, window[rel], accTree[prop][rel]);
+ break;
+ }
+
+ case "role":
+ isRole(acc, accTree[prop], msg);
+ break;
+
+ case "states":
+ case "extraStates":
+ case "absentStates":
+ case "absentExtraStates": {
+ testStates(acc, accTree["states"], accTree["extraStates"],
+ accTree["absentStates"], accTree["absentExtraStates"]);
+ break;
+ }
+
+ case "tagName":
+ is(accTree[prop], acc.DOMNode.tagName, msg);
+ break;
+
+ case "textAttrs": {
+ var prevOffset = -1;
+ for (var offset in accTree[prop]) {
+ if (prevOffset !=- 1) {
+ var attrs = accTree[prop][prevOffset];
+ testTextAttrs(acc, prevOffset, attrs, { }, prevOffset, +offset, true);
+ }
+ prevOffset = +offset;
+ }
+
+ if (prevOffset != -1) {
+ var charCount = getAccessible(acc, [nsIAccessibleText]).characterCount;
+ var attrs = accTree[prop][prevOffset];
+ testTextAttrs(acc, prevOffset, attrs, { }, prevOffset, charCount, true);
+ }
+
+ break;
+ }
+
+ default:
+ if (prop.indexOf("todo_") == 0)
+ todo(false, msg);
+ else if (prop != "children")
+ is(acc[prop], accTree[prop], msg);
+ }
+ }
+
+ // Test children.
+ if ("children" in accTree && accTree["children"] instanceof Array) {
+ var children = acc.children;
+ var childCount = children.length;
+
+ if (accTree.children.length != childCount) {
+ for (var i = 0; i < Math.max(accTree.children.length, childCount); i++) {
+ var accChild = null, testChild = null;
+ try {
+ testChild = accTree.children[i];
+ accChild = children.queryElementAt(i, nsIAccessible);
+
+ if (!testChild) {
+ ok(false, prettyName(acc) + " has an extra child at index " + i +
+ " : " + prettyName(accChild));
+ continue;
+ }
+
+ testChild = normalizeAccTreeObj(testChild);
+ if (accChild.role !== testChild.role) {
+ ok(false, prettyName(accTree) + " and " + prettyName(acc) +
+ " have different children at index " + i + " : " +
+ prettyName(testChild) + ", " + prettyName(accChild));
+ }
+ info("Matching " + prettyName(accTree) + " and " + prettyName(acc) +
+ " child at index " + i + " : " + prettyName(accChild));
+
+ } catch (e) {
+ ok(false, prettyName(accTree) + " is expected to have a child at index " + i +
+ " : " + prettyName(testChild) + ", original tested: " +
+ prettyName(aAccOrElmOrID) + ", " + e);
+ }
+ }
+ } else {
+ if (aFlags & kSkipTreeFullCheck) {
+ for (var i = 0; i < childCount; i++) {
+ var child = children.queryElementAt(i, nsIAccessible);
+ testAccessibleTree(child, accTree.children[i], aFlags);
+ }
+ return;
+ }
+
+ // nsIAccessible::firstChild
+ var expectedFirstChild = childCount > 0 ?
+ children.queryElementAt(0, nsIAccessible) : null;
+ var firstChild = null;
+ try { firstChild = acc.firstChild; } catch (e) {}
+ is(firstChild, expectedFirstChild,
+ "Wrong first child of " + prettyName(acc));
+
+ // nsIAccessible::lastChild
+ var expectedLastChild = childCount > 0 ?
+ children.queryElementAt(childCount - 1, nsIAccessible) : null;
+ var lastChild = null;
+ try { lastChild = acc.lastChild; } catch (e) {}
+ is(lastChild, expectedLastChild,
+ "Wrong last child of " + prettyName(acc));
+
+ for (var i = 0; i < childCount; i++) {
+ var child = children.queryElementAt(i, nsIAccessible);
+
+ // nsIAccessible::parent
+ var parent = null;
+ try { parent = child.parent; } catch (e) {}
+ is(parent, acc, "Wrong parent of " + prettyName(child));
+
+ // nsIAccessible::indexInParent
+ var indexInParent = -1;
+ try { indexInParent = child.indexInParent; } catch(e) {}
+ is(indexInParent, i,
+ "Wrong index in parent of " + prettyName(child));
+
+ // nsIAccessible::nextSibling
+ var expectedNextSibling = (i < childCount - 1) ?
+ children.queryElementAt(i + 1, nsIAccessible) : null;
+ var nextSibling = null;
+ try { nextSibling = child.nextSibling; } catch (e) {}
+ is(nextSibling, expectedNextSibling,
+ "Wrong next sibling of " + prettyName(child));
+
+ // nsIAccessible::previousSibling
+ var expectedPrevSibling = (i > 0) ?
+ children.queryElementAt(i - 1, nsIAccessible) : null;
+ var prevSibling = null;
+ try { prevSibling = child.previousSibling; } catch (e) {}
+ is(prevSibling, expectedPrevSibling,
+ "Wrong previous sibling of " + prettyName(child));
+
+ // Go down through subtree
+ testAccessibleTree(child, accTree.children[i], aFlags);
+ }
+ }
+ }
+}
+
+/**
+ * Return true if accessible for the given node is in cache.
+ */
+function isAccessibleInCache(aNodeOrId)
+{
+ var node = getNode(aNodeOrId);
+ return gAccService.getAccessibleFromCache(node) ? true : false;
+}
+
+/**
+ * Test accessible tree for defunct accessible.
+ *
+ * @param aAcc [in] the defunct accessible
+ * @param aNodeOrId [in] the DOM node identifier for the defunct accessible
+ */
+function testDefunctAccessible(aAcc, aNodeOrId)
+{
+ if (aNodeOrId)
+ ok(!isAccessible(aNodeOrId),
+ "Accessible for " + aNodeOrId + " wasn't properly shut down!");
+
+ var msg = " doesn't fail for shut down accessible " +
+ prettyName(aNodeOrId) + "!";
+
+ // firstChild
+ var success = false;
+ try {
+ aAcc.firstChild;
+ } catch (e) {
+ success = (e.result == Components.results.NS_ERROR_FAILURE)
+ }
+ ok(success, "firstChild" + msg);
+
+ // lastChild
+ success = false;
+ try {
+ aAcc.lastChild;
+ } catch (e) {
+ success = (e.result == Components.results.NS_ERROR_FAILURE)
+ }
+ ok(success, "lastChild" + msg);
+
+ // childCount
+ success = false;
+ try {
+ aAcc.childCount;
+ } catch (e) {
+ success = (e.result == Components.results.NS_ERROR_FAILURE)
+ }
+ ok(success, "childCount" + msg);
+
+ // children
+ success = false;
+ try {
+ aAcc.children;
+ } catch (e) {
+ success = (e.result == Components.results.NS_ERROR_FAILURE)
+ }
+ ok(success, "children" + msg);
+
+ // nextSibling
+ success = false;
+ try {
+ aAcc.nextSibling;
+ } catch (e) {
+ success = (e.result == Components.results.NS_ERROR_FAILURE);
+ }
+ ok(success, "nextSibling" + msg);
+
+ // previousSibling
+ success = false;
+ try {
+ aAcc.previousSibling;
+ } catch (e) {
+ success = (e.result == Components.results.NS_ERROR_FAILURE);
+ }
+ ok(success, "previousSibling" + msg);
+
+ // parent
+ success = false;
+ try {
+ aAcc.parent;
+ } catch (e) {
+ success = (e.result == Components.results.NS_ERROR_FAILURE);
+ }
+ ok(success, "parent" + msg);
+}
+
+/**
+ * Convert role to human readable string.
+ */
+function roleToString(aRole)
+{
+ return gAccService.getStringRole(aRole);
+}
+
+/**
+ * Convert states to human readable string.
+ */
+function statesToString(aStates, aExtraStates)
+{
+ var list = gAccService.getStringStates(aStates, aExtraStates);
+
+ var str = "";
+ for (var index = 0; index < list.length - 1; index++)
+ str += list.item(index) + ", ";
+
+ if (list.length != 0)
+ str += list.item(index)
+
+ return str;
+}
+
+/**
+ * Convert event type to human readable string.
+ */
+function eventTypeToString(aEventType)
+{
+ return gAccService.getStringEventType(aEventType);
+}
+
+/**
+ * Convert relation type to human readable string.
+ */
+function relationTypeToString(aRelationType)
+{
+ return gAccService.getStringRelationType(aRelationType);
+}
+
+function getLoadContext() {
+ const Ci = Components.interfaces;
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext);
+}
+
+/**
+ * Return text from clipboard.
+ */
+function getTextFromClipboard()
+{
+ var clip = Components.classes["@mozilla.org/widget/clipboard;1"].
+ getService(Components.interfaces.nsIClipboard);
+ if (!clip)
+ return "";
+
+ var trans = Components.classes["@mozilla.org/widget/transferable;1"].
+ createInstance(Components.interfaces.nsITransferable);
+ trans.init(getLoadContext());
+ if (!trans)
+ return "";
+
+ trans.addDataFlavor("text/unicode");
+ clip.getData(trans, clip.kGlobalClipboard);
+
+ var str = new Object();
+ var strLength = new Object();
+ trans.getTransferData("text/unicode", str, strLength);
+
+ if (str)
+ str = str.value.QueryInterface(Components.interfaces.nsISupportsString);
+ if (str)
+ return str.data.substring(0, strLength.value / 2);
+
+ return "";
+}
+
+/**
+ * Extract DOMNode id from an accessible. If e10s is enabled, DOMNode is not
+ * present in parent process but, if available, DOMNode id is attached to an
+ * accessible object.
+ * @param {nsIAccessible} accessible accessible
+ * @return {String?} DOMNode id if available
+ */
+function getAccessibleDOMNodeID(accessible) {
+ if (accessible instanceof nsIAccessibleDocument) {
+ // If accessible is a document, trying to find its document body id.
+ try {
+ return accessible.DOMNode.body.id;
+ } catch (e) { /* This only works if accessible is not a proxy. */ }
+ }
+ try {
+ return accessible.DOMNode.id;
+ } catch (e) { /* This will fail if DOMNode is in different process. */ }
+ try {
+ // When e10s is enabled, accessible will have an "id" property if its
+ // corresponding DOMNode has an id. If accessible is a document, its "id"
+ // property corresponds to the "id" of its body element.
+ return accessible.id;
+ } catch (e) { /* This will fail if accessible is not a proxy. */ }
+}
+
+/**
+ * Return pretty name for identifier, it may be ID, DOM node or accessible.
+ */
+function prettyName(aIdentifier)
+{
+ if (aIdentifier instanceof Array) {
+ var msg = "";
+ for (var idx = 0; idx < aIdentifier.length; idx++) {
+ if (msg != "")
+ msg += ", ";
+
+ msg += prettyName(aIdentifier[idx]);
+ }
+ return msg;
+ }
+
+ if (aIdentifier instanceof nsIAccessible) {
+ var acc = getAccessible(aIdentifier);
+ var domID = getAccessibleDOMNodeID(acc);
+ var msg = "[";
+ try {
+ if (Services.appinfo.browserTabsRemoteAutostart) {
+ if (domID) {
+ msg += `DOM node id: ${domID}, `;
+ }
+ } else {
+ msg += `${getNodePrettyName(acc.DOMNode)}, `;
+ }
+ msg += "role: " + roleToString(acc.role);
+ if (acc.name)
+ msg += ", name: '" + shortenString(acc.name) + "'";
+ } catch (e) {
+ msg += "defunct";
+ }
+
+ if (acc)
+ msg += ", address: " + getObjAddress(acc);
+ msg += "]";
+
+ return msg;
+ }
+
+ if (aIdentifier instanceof nsIDOMNode)
+ return "[ " + getNodePrettyName(aIdentifier) + " ]";
+
+ if (aIdentifier && typeof aIdentifier === "object" ) {
+ var treeObj = normalizeAccTreeObj(aIdentifier);
+ if ("role" in treeObj) {
+ function stringifyTree(aObj) {
+ var text = roleToString(aObj.role) + ": [ ";
+ if ("children" in aObj) {
+ for (var i = 0; i < aObj.children.length; i++) {
+ var c = normalizeAccTreeObj(aObj.children[i]);
+ text += stringifyTree(c);
+ if (i < aObj.children.length - 1) {
+ text += ", ";
+ }
+ }
+ }
+ return text + "] ";
+ }
+ return `{ ${stringifyTree(treeObj)} }`;
+ }
+ return JSON.stringify(aIdentifier);
+ }
+
+ return " '" + aIdentifier + "' ";
+}
+
+/**
+ * Shorten a long string if it exceeds MAX_TRIM_LENGTH.
+ * @param aString the string to shorten.
+ * @returns the shortened string.
+ */
+function shortenString(aString, aMaxLength)
+{
+ if (aString.length <= MAX_TRIM_LENGTH)
+ return aString;
+
+ // Trim the string if its length is > MAX_TRIM_LENGTH characters.
+ var trimOffset = MAX_TRIM_LENGTH / 2;
+ return aString.substring(0, trimOffset - 1) + "..." +
+ aString.substring(aString.length - trimOffset, aString.length);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// General Utils
+////////////////////////////////////////////////////////////////////////////////
+/**
+ * Return main chrome window (crosses chrome boundary)
+ */
+function getMainChromeWindow(aWindow)
+{
+ return aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindow);
+}
+
+/** Sets the test plugin(s) initially expected enabled state.
+ * It will automatically be reset to it's previous value after the test
+ * ends.
+ * @param aNewEnabledState [in] the enabled state, e.g. SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED
+ * @param aPluginName [in, optional] The name of the plugin, defaults to "Test Plug-in"
+ */
+function setTestPluginEnabledState(aNewEnabledState, aPluginName)
+{
+ var plugin = getTestPluginTag(aPluginName);
+ var oldEnabledState = plugin.enabledState;
+ plugin.enabledState = aNewEnabledState;
+ SimpleTest.registerCleanupFunction(function() {
+ getTestPluginTag(aPluginName).enabledState = oldEnabledState;
+ });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Private
+////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible general
+
+function getNodePrettyName(aNode)
+{
+ try {
+ var tag = "";
+ if (aNode.nodeType == nsIDOMNode.DOCUMENT_NODE) {
+ tag = "document";
+ } else {
+ tag = aNode.localName;
+ if (aNode.nodeType == nsIDOMNode.ELEMENT_NODE && aNode.hasAttribute("id"))
+ tag += "@id=\"" + aNode.getAttribute("id") + "\"";
+ }
+
+ return "'" + tag + " node', address: " + getObjAddress(aNode);
+ } catch (e) {
+ return "' no node info '";
+ }
+}
+
+function getObjAddress(aObj)
+{
+ var exp = /native\s*@\s*(0x[a-f0-9]+)/g;
+ var match = exp.exec(aObj.toString());
+ if (match)
+ return match[1];
+
+ return aObj.toString();
+}
+
+function getTestPluginTag(aPluginName)
+{
+ var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"]
+ .getService(SpecialPowers.Ci.nsIPluginHost);
+ var tags = ph.getPluginTags();
+ var name = aPluginName || "Test Plug-in";
+ for (var tag of tags) {
+ if (tag.name == name) {
+ return tag;
+ }
+ }
+
+ ok(false, "Could not find plugin tag with plugin name '" + name + "'");
+ return null;
+}
+
+function normalizeAccTreeObj(aObj)
+{
+ var key = Object.keys(aObj)[0];
+ var roleName = "ROLE_" + key;
+ if (roleName in nsIAccessibleRole) {
+ return {
+ role: nsIAccessibleRole[roleName],
+ children: aObj[key]
+ };
+ }
+ return aObj;
+}
diff --git a/accessible/tests/mochitest/dumbfile.zip b/accessible/tests/mochitest/dumbfile.zip
new file mode 100644
index 000000000..15cb0ecb3
--- /dev/null
+++ b/accessible/tests/mochitest/dumbfile.zip
Binary files differ
diff --git a/accessible/tests/mochitest/editabletext/a11y.ini b/accessible/tests/mochitest/editabletext/a11y.ini
new file mode 100644
index 000000000..68466fdf2
--- /dev/null
+++ b/accessible/tests/mochitest/editabletext/a11y.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+support-files =
+ editabletext.js
+ !/accessible/tests/mochitest/*.js
+
+[test_1.html]
+[test_2.html]
diff --git a/accessible/tests/mochitest/editabletext/editabletext.js b/accessible/tests/mochitest/editabletext/editabletext.js
new file mode 100644
index 000000000..2fb1851bf
--- /dev/null
+++ b/accessible/tests/mochitest/editabletext/editabletext.js
@@ -0,0 +1,353 @@
+/**
+ * Perform all editable text tests.
+ */
+function editableTextTestRun()
+{
+ this.add = function add(aTest)
+ {
+ this.seq.push(aTest);
+ }
+
+ this.run = function run()
+ {
+ this.iterate();
+ }
+
+ this.index = 0;
+ this.seq = new Array();
+
+ this.iterate = function iterate()
+ {
+ if (this.index < this.seq.length) {
+ this.seq[this.index++].startTest(this);
+ return;
+ }
+
+ this.seq = null;
+ SimpleTest.finish();
+ }
+}
+
+/**
+ * Used to test nsIEditableTextAccessible methods.
+ */
+function editableTextTest(aID)
+{
+ /**
+ * Schedule a test, the given function with its arguments will be executed
+ * when preceding test is complete.
+ */
+ this.scheduleTest = function scheduleTest(aFunc)
+ {
+ // A data container acts like a dummy invoker, it's never invoked but
+ // it's used to generate real invoker when previous invoker was handled.
+ var dataContainer = {
+ func: aFunc,
+ funcArgs: Array.slice(arguments, 1)
+ };
+ this.mEventQueue.push(dataContainer);
+
+ if (!this.mEventQueueReady) {
+ this.unwrapNextTest();
+ this.mEventQueueReady = true;
+ }
+ }
+
+ /**
+ * setTextContents test.
+ */
+ this.setTextContents = function setTextContents(aValue, aSkipStartOffset)
+ {
+ var testID = "setTextContents '" + aValue + "' for " + prettyName(aID);
+
+ function setTextContentsInvoke()
+ {
+ dump(`\nsetTextContents '${aValue}'\n`);
+ var acc = getAccessible(aID, nsIAccessibleEditableText);
+ acc.setTextContents(aValue);
+ }
+
+ aSkipStartOffset = aSkipStartOffset || 0;
+ var insertTripple = aValue ?
+ [ aSkipStartOffset, aSkipStartOffset + aValue.length, aValue ] : null;
+ var oldValue = getValue(aID);
+ var removeTripple = oldValue ?
+ [ aSkipStartOffset, aSkipStartOffset + oldValue.length, oldValue ] : null;
+
+ this.generateTest(aID, removeTripple, insertTripple, setTextContentsInvoke,
+ getValueChecker(aID, aValue), testID);
+ }
+
+ /**
+ * insertText test.
+ */
+ this.insertText = function insertText(aStr, aPos, aResStr, aResPos)
+ {
+ var testID = "insertText '" + aStr + "' at " + aPos + " for " +
+ prettyName(aID);
+
+ function insertTextInvoke()
+ {
+ dump(`\ninsertText '${aStr}' at ${aPos} pos\n`);
+ var acc = getAccessible(aID, nsIAccessibleEditableText);
+ acc.insertText(aStr, aPos);
+ }
+
+ var resPos = (aResPos != undefined) ? aResPos : aPos;
+ this.generateTest(aID, null, [resPos, resPos + aStr.length, aStr],
+ insertTextInvoke, getValueChecker(aID, aResStr), testID);
+ }
+
+ /**
+ * copyText test.
+ */
+ this.copyText = function copyText(aStartPos, aEndPos, aClipboardStr)
+ {
+ var testID = "copyText from " + aStartPos + " to " + aEndPos + " for " +
+ prettyName(aID);
+
+ function copyTextInvoke()
+ {
+ var acc = getAccessible(aID, nsIAccessibleEditableText);
+ acc.copyText(aStartPos, aEndPos);
+ }
+
+ this.generateTest(aID, null, null, copyTextInvoke,
+ getClipboardChecker(aID, aClipboardStr), testID);
+ }
+
+ /**
+ * copyText and pasteText test.
+ */
+ this.copyNPasteText = function copyNPasteText(aStartPos, aEndPos,
+ aPos, aResStr)
+ {
+ var testID = "copyText from " + aStartPos + " to " + aEndPos +
+ "and pasteText at " + aPos + " for " + prettyName(aID);
+
+ function copyNPasteTextInvoke()
+ {
+ var acc = getAccessible(aID, nsIAccessibleEditableText);
+ acc.copyText(aStartPos, aEndPos);
+ acc.pasteText(aPos);
+ }
+
+ this.generateTest(aID, null, [aStartPos, aEndPos, getTextFromClipboard],
+ copyNPasteInvoke, getValueChecker(aID, aResStr), testID);
+ }
+
+ /**
+ * cutText test.
+ */
+ this.cutText = function cutText(aStartPos, aEndPos, aResStr,
+ aResStartPos, aResEndPos)
+ {
+ var testID = "cutText from " + aStartPos + " to " + aEndPos + " for " +
+ prettyName(aID);
+
+ function cutTextInvoke()
+ {
+ var acc = getAccessible(aID, nsIAccessibleEditableText);
+ acc.cutText(aStartPos, aEndPos);
+ }
+
+ var resStartPos = (aResStartPos != undefined) ? aResStartPos : aStartPos;
+ var resEndPos = (aResEndPos != undefined) ? aResEndPos : aEndPos;
+ this.generateTest(aID, [resStartPos, resEndPos, getTextFromClipboard], null,
+ cutTextInvoke, getValueChecker(aID, aResStr), testID);
+ }
+
+ /**
+ * cutText and pasteText test.
+ */
+ this.cutNPasteText = function copyNPasteText(aStartPos, aEndPos,
+ aPos, aResStr)
+ {
+ var testID = "cutText from " + aStartPos + " to " + aEndPos +
+ " and pasteText at " + aPos + " for " + prettyName(aID);
+
+ function cutNPasteTextInvoke()
+ {
+ var acc = getAccessible(aID, nsIAccessibleEditableText);
+ acc.cutText(aStartPos, aEndPos);
+ acc.pasteText(aPos);
+ }
+
+ this.generateTest(aID, [aStartPos, aEndPos, getTextFromClipboard],
+ [aPos, -1, getTextFromClipboard],
+ cutNPasteTextInvoke, getValueChecker(aID, aResStr),
+ testID);
+ }
+
+ /**
+ * pasteText test.
+ */
+ this.pasteText = function pasteText(aPos, aResStr)
+ {
+ var testID = "pasteText at " + aPos + " for " + prettyName(aID);
+
+ function pasteTextInvoke()
+ {
+ var acc = getAccessible(aID, nsIAccessibleEditableText);
+ acc.pasteText(aPos);
+ }
+
+ this.generateTest(aID, null, [aPos, -1, getTextFromClipboard],
+ pasteTextInvoke, getValueChecker(aID, aResStr), testID);
+ }
+
+ /**
+ * deleteText test.
+ */
+ this.deleteText = function deleteText(aStartPos, aEndPos, aResStr)
+ {
+ var testID = "deleteText from " + aStartPos + " to " + aEndPos +
+ " for " + prettyName(aID);
+
+ var oldValue = getValue(aID).substring(aStartPos, aEndPos);
+ var removeTripple = oldValue ? [aStartPos, aEndPos, oldValue] : null;
+
+ function deleteTextInvoke()
+ {
+ var acc = getAccessible(aID, [nsIAccessibleEditableText]);
+ acc.deleteText(aStartPos, aEndPos);
+ }
+
+ this.generateTest(aID, removeTripple, null, deleteTextInvoke,
+ getValueChecker(aID, aResStr), testID);
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Implementation details.
+
+ function getValue(aID)
+ {
+ var value = "";
+ var elm = getNode(aID);
+ if (elm instanceof Components.interfaces.nsIDOMNSEditableElement)
+ return elm.value;
+
+ if (elm instanceof Components.interfaces.nsIDOMHTMLDocument)
+ return elm.body.textContent;
+
+ return elm.textContent;
+ }
+
+ /**
+ * Common checkers.
+ */
+ function getValueChecker(aID, aValue)
+ {
+ var checker = {
+ check: function valueChecker_check()
+ {
+ is(getValue(aID), aValue, "Wrong value " + aValue);
+ }
+ };
+ return checker;
+ }
+
+ function getClipboardChecker(aID, aText)
+ {
+ var checker = {
+ check: function clipboardChecker_check()
+ {
+ is(getTextFromClipboard(), aText, "Wrong text in clipboard.");
+ }
+ };
+ return checker;
+ }
+
+ function getValueNClipboardChecker(aID, aValue, aText)
+ {
+ var valueChecker = getValueChecker(aID, aValue);
+ var clipboardChecker = getClipboardChecker(aID, aText);
+
+ var checker = {
+ check: function()
+ {
+ valueChecker.check();
+ clipboardChecker.check();
+ }
+ };
+ return checker;
+ }
+
+ /**
+ * Process next scheduled test.
+ */
+ this.unwrapNextTest = function unwrapNextTest()
+ {
+ var data = this.mEventQueue.mInvokers[this.mEventQueue.mIndex + 1];
+ if (data)
+ data.func.apply(this, data.funcArgs);
+ }
+
+ /**
+ * Used to generate an invoker object for the sheduled test.
+ */
+ this.generateTest = function generateTest(aID, aRemoveTriple, aInsertTriple,
+ aInvokeFunc, aChecker, aInvokerID)
+ {
+ var et = this;
+ var invoker = {
+ eventSeq: [],
+
+ invoke: aInvokeFunc,
+ finalCheck: function finalCheck()
+ {
+ //dumpTree(aID, `'${aID}' tree:`);
+
+ aChecker.check();
+ et.unwrapNextTest(); // replace dummy invoker on real invoker object.
+ },
+ getID: function getID() { return aInvokerID; }
+ };
+
+ if (aRemoveTriple) {
+ var checker = new textChangeChecker(aID, aRemoveTriple[0],
+ aRemoveTriple[1], aRemoveTriple[2],
+ false);
+ invoker.eventSeq.push(checker);
+ }
+
+ if (aInsertTriple) {
+ var checker = new textChangeChecker(aID, aInsertTriple[0],
+ aInsertTriple[1], aInsertTriple[2],
+ true);
+ invoker.eventSeq.push(checker);
+ }
+
+ // Claim that we don't want to fail when no events are expected.
+ if (!aRemoveTriple && !aInsertTriple)
+ invoker.noEventsOnAction = true;
+
+ this.mEventQueue.mInvokers[this.mEventQueue.mIndex + 1] = invoker;
+ }
+
+ /**
+ * Run the tests.
+ */
+ this.startTest = function startTest(aTestRun)
+ {
+ var testRunObj = aTestRun;
+ var thisObj = this;
+ this.mEventQueue.onFinish = function finishCallback()
+ {
+ // Notify textRun object that all tests were finished.
+ testRunObj.iterate();
+
+ // Help GC to avoid leaks (refer to aTestRun from local variable, drop
+ // onFinish function).
+ thisObj.mEventQueue.onFinish = null;
+
+ return DO_NOT_FINISH_TEST;
+ }
+
+ this.mEventQueue.invoke();
+ }
+
+ this.mEventQueue = new eventQueue();
+ this.mEventQueueReady = false;
+}
+
diff --git a/accessible/tests/mochitest/editabletext/test_1.html b/accessible/tests/mochitest/editabletext/test_1.html
new file mode 100644
index 000000000..0c0b19696
--- /dev/null
+++ b/accessible/tests/mochitest/editabletext/test_1.html
@@ -0,0 +1,144 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=452161
+-->
+<head>
+ <title>nsIAccessibleEditableText chrome tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="editabletext.js"></script>
+
+ <script type="application/javascript">
+ //gA11yEventDumpToConsole = true;
+ //enableLogging("tree,verbose"); // debug
+
+ function addTestEditable(aID, aTestRun, aBeforeContent, aAfterContent)
+ {
+ var et = new editableTextTest(aID);
+ var startOffset = aBeforeContent ? aBeforeContent.length : 0;
+ // XXX afterContent currently is not used
+
+ //////////////////////////////////////////////////////////////////////////
+ // setTextContents
+ et.scheduleTest(et.setTextContents, "hello", startOffset);
+ et.scheduleTest(et.setTextContents, "olleh", startOffset);
+ et.scheduleTest(et.setTextContents, "", startOffset);
+
+ //////////////////////////////////////////////////////////////////////////
+ // insertText
+ et.scheduleTest(et.insertText, "hello", startOffset, "hello");
+ et.scheduleTest(et.insertText, "ma ", startOffset, "ma hello");
+ et.scheduleTest(et.insertText, "ma", startOffset + 2, "mama hello");
+ et.scheduleTest(et.insertText, " hello", startOffset + 10, "mama hello hello");
+
+ // XXX: bug 452584
+
+ //////////////////////////////////////////////////////////////////////////
+ // deleteText
+// et.deleteText(0, 5, "hello hello");
+// et.deleteText(5, 6, "hellohello");
+// et.deleteText(5, 10, "hello");
+// et.deleteText(0, 5, "");
+
+ //////////////////////////////////////////////////////////////////////////
+ // copyNPasteText
+// et.copyNPasteText(0, 0, 0, "");
+// et.insertText("hello", 0, "hello");
+// et.copyNPasteText(0, 1, 0, "hhello");
+// et.copyNPasteText(5, 6, 6, "hhelloo");
+// et.copyNPasteText(3, 4, 1, "hehelloo");
+
+ //////////////////////////////////////////////////////////////////////////
+// // cutNPasteText
+// et.cutNPasteText(0, 1, 1, "ehhelloo");
+// et.cutNPasteText(1, 2, 0, "hehelloo");
+// et.cutNPasteText(7, 8, 8, "hehelloo");
+
+ aTestRun.add(et);
+ }
+
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ function runTest()
+ {
+ var testRun = new editableTextTestRun();
+
+ addTestEditable("input", testRun);
+ addTestEditable("div", testRun);
+ addTestEditable("divb", testRun, "pseudo element", "");
+ addTestEditable("diva", testRun, "", "pseudo element");
+ addTestEditable("divba", testRun, "before", "after");
+ addTestEditable(getNode("frame").contentDocument, testRun);
+
+ testRun.run(); // Will call SimpleTest.finish();
+ }
+
+ function doTest()
+ {
+ // Prepare tested elements.
+
+ // Design mode on/off triggers an editable state change event on
+ // the document accessible.
+ var frame = getNode("frame");
+ waitForEvent(EVENT_STATE_CHANGE, frame.contentDocument, runTest);
+ frame.contentDocument.designMode = "on";
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+ <style>
+ #divb::before,
+ #diva::after {
+ content: "pseudo element";
+ }
+ #divba::before {
+ content: "before";
+ }
+ #divba::after {
+ content: "after";
+ }
+ </style>
+</head>
+<body>
+
+ <a target="_blank"
+ title="nsIAccessibleEditableText chrome tests"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=452161">
+ Bug 452161
+ </a>
+ <a target="_blank"
+ title="Cache rendered text on a11y side"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=626660">
+ Bug 626660
+ </a>
+ <a target="_blank"
+ title="Pseudo element support test"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1105611">
+ Bug 1105611
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="input"/>
+
+ <div id="div" contenteditable="true"></div>
+ <div id="divb" contenteditable="true"></div>
+ <div id="diva" contenteditable="true"></div>
+ <div id="divba" contenteditable="true"></div>
+
+ <iframe id="frame"/>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/editabletext/test_2.html b/accessible/tests/mochitest/editabletext/test_2.html
new file mode 100644
index 000000000..5d96ebf35
--- /dev/null
+++ b/accessible/tests/mochitest/editabletext/test_2.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>nsIAccessibleEditableText chrome tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="editabletext.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ var et = new editableTextTest("input");
+
+ // 'ee' insertion/removal at 1 or 2 offset of 'hello'/'heeello' string
+ // reports 'ee' text was inserted/removed at 2 offset.
+ et.scheduleTest(et.insertText, "ee", 1, "heeello", 2);
+ et.scheduleTest(et.copyText, 1, 3, "ee");
+ et.scheduleTest(et.cutText, 1, 3, "hello", 2, 4);
+ et.scheduleTest(et.insertText, "ee", 2, "heeello", 2);
+ et.scheduleTest(et.cutText, 2, 4, "hello", 2, 4);
+
+ et.scheduleTest(et.deleteText, 1, 3, "hlo");
+ et.scheduleTest(et.pasteText, 1, "heelo");
+
+ var testRun = new editableTextTestRun();
+ testRun.add(et);
+ testRun.run(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="HyperText accessible should get focus when the caret is positioned inside of it, text is changed or copied into clipboard by ATs"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=524115">
+ Mozilla Bug 524115
+ </a>
+ <a target="_blank"
+ title="Cache rendered text on a11y side"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=626660">
+ Mozilla Bug 626660
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="input" value="hello"/>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/elm/a11y.ini b/accessible/tests/mochitest/elm/a11y.ini
new file mode 100644
index 000000000..76fb4402b
--- /dev/null
+++ b/accessible/tests/mochitest/elm/a11y.ini
@@ -0,0 +1,16 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/moz.png
+ !/dom/media/test/bug461281.ogg
+
+[test_HTMLSpec.html]
+skip-if = buildapp == 'mulet'
+[test_figure.html]
+[test_listbox.xul]
+[test_MathMLSpec.html]
+[test_nsApplicationAcc.html]
+[test_plugin.html]
+skip-if = buildapp == 'mulet'
+[test_canvas.html]
+[test_shadowroot.html]
diff --git a/accessible/tests/mochitest/elm/test_HTMLSpec.html b/accessible/tests/mochitest/elm/test_HTMLSpec.html
new file mode 100644
index 000000000..f5f48ec56
--- /dev/null
+++ b/accessible/tests/mochitest/elm/test_HTMLSpec.html
@@ -0,0 +1,1671 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML a11y spec tests</title>
+ <link id="link" rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../actions.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+ <script type="application/javascript"
+ src="../relations.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:a@href
+
+ var obj = {
+ role: ROLE_LINK,
+ states: STATE_LINKED,
+ actions: "jump",
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText, nsIAccessibleHyperLink ],
+ children: [ // all kids inherits linked state and jump action
+ {
+ role: ROLE_TEXT_LEAF,
+ states: STATE_LINKED,
+ actions: "jump"
+ }
+ ]
+ };
+ testElm("a_href", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:a no @href
+
+ obj = {
+ todo_role: ROLE_TEXT_CONTAINER,
+ absentStates: STATE_LINKED,
+ actions: null,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ absentStates: STATE_LINKED,
+ actions: null
+ }
+ ]
+ };
+ testElm("a_nohref", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:abbr contained by HTML:td
+
+ obj = {
+ role: ROLE_CELL,
+ attributes: { abbr: "WWW" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ children: [
+ {
+ role: ROLE_TEXT,
+ children: [ { role: ROLE_TEXT_LEAF } ]
+ }
+ ]
+ };
+ testElm("td_abbr", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:address
+
+ obj = {
+ role: ROLE_TEXT_CONTAINER,
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ };
+ testElm("address", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:area@href
+
+ obj = {
+ role: ROLE_LINK,
+ states: STATE_LINKED,
+ actions: "jump",
+ interfaces: [ nsIAccessibleHyperLink ],
+ children: []
+ };
+ testElm(getAccessible("imgmap").firstChild, obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:area no @href
+
+ obj = {
+ todo_role: "ROLE_SHAPE",
+ absentStates: STATE_LINKED,
+ children: []
+ };
+ testElm(getAccessible("imgmap").lastChild, obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:article
+ obj = {
+ role: ROLE_DOCUMENT,
+ states: STATE_READONLY,
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ };
+ testElm("article", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:aside
+ obj = {
+ role: ROLE_NOTE,
+ attributes: { "xml-roles": "complementary" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
+ };
+ testElm("aside", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ obj = { // HTML:audio
+ role: ROLE_GROUPING
+ };
+ testElm("audio", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ obj = { // HTML:b contained by paragraph
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "font-weight": kBoldFontWeight }
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF } // HTML:b text
+ ]
+ }
+ testElm("b_container", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ obj = { // HTML:bdi contained by paragraph
+ role: ROLE_PARAGRAPH,
+ todo_textAttrs: {
+ 0: { },
+ 5: { "writing-mode": "rl" },
+ 8: { }
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF }, // HTML:bdi text
+ { role: ROLE_TEXT_LEAF } // plain text
+ ]
+ }
+ testElm("bdi_container", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:bdo contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ todo_textAttrs: {
+ 0: { },
+ 6: { "writing-mode": "rl" }
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ ]
+ }
+ testElm("bdo_container", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:blockquote
+
+ obj = {
+ role: ROLE_SECTION,
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ children: [ { role: ROLE_PARAGRAPH } ]
+ };
+ testElm("blockquote", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:br contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ children: [ { role: ROLE_WHITESPACE } ]
+ };
+ testElm("br_container", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ obj = { // HTML:button
+ role: ROLE_PUSHBUTTON,
+ absentStates: STATE_DEFAULT,
+ actions: "press",
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
+ };
+ testElm("button", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:button@type="submit" (default button)
+
+ obj = {
+ role: ROLE_PUSHBUTTON,
+ states: STATE_DEFAULT,
+ actions: "press"
+ };
+ testElm("button_default", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:canvas
+
+ obj = {
+ role: ROLE_CANVAS
+ };
+ testElm("canvas", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:caption under table
+
+ obj = {
+ role: ROLE_TABLE,
+ relations: {
+ RELATION_LABELLED_BY: "caption"
+ },
+ interfaces: nsIAccessibleTable,
+ children: [
+ {
+ role: ROLE_CAPTION,
+ relations: {
+ RELATION_LABEL_FOR: "table"
+ },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
+ },
+ { // td inside thead
+ role: ROLE_ROW,
+ children: [
+ {
+ role: ROLE_COLUMNHEADER,
+ interfaces: [ nsIAccessibleTableCell, nsIAccessibleText, nsIAccessibleHyperText ]
+ },
+ { role: ROLE_COLUMNHEADER }
+ ]
+ },
+ { // td inside tbody
+ role: ROLE_ROW,
+ children: [
+ {
+ role: ROLE_ROWHEADER,
+ interfaces: [ nsIAccessibleTableCell, nsIAccessibleText, nsIAccessibleHyperText ]
+ },
+ {
+ role: ROLE_CELL,
+ interfaces: [ nsIAccessibleTableCell, nsIAccessibleText, nsIAccessibleHyperText ]
+ }
+ ]
+ },
+ { // td inside tfoot
+ role: ROLE_ROW
+ }
+ ]
+ };
+ testElm("table", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:cite contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "font-style": "italic" }
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF } // HTML:cite text
+ ]
+ };
+ testElm("cite_container", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:code contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "font-family": kMonospaceFontFamily }
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF } // HTML:code text
+ ]
+ };
+ testElm("code_container", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:col and HTML:colgroup under table
+
+ obj =
+ { TABLE : [
+ { ROW :[
+ { role: ROLE_CELL },
+ { role: ROLE_CELL },
+ { role: ROLE_CELL }
+ ] }
+ ] };
+ testElm("colNcolgroup_table", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:data contained by paragraph
+
+ obj =
+ { PARAGRAPH: [
+ { TEXT_LEAF: [] } // HTML:data text
+ ] };
+ testElm("data_container", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:datalist associated with input
+
+ todo(false, "datalist and summary tree hierarchy test missed");
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:dd, HTML:dl, HTML:dd
+
+ obj = {
+ role: ROLE_DEFINITION_LIST,
+ states: STATE_READONLY,
+ children: [ // dl
+ {
+ role: ROLE_TERM,
+ states: STATE_READONLY,
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ children: [ // dt
+ { role: ROLE_TEXT_LEAF }
+ ]
+ },
+ {
+ role: ROLE_DEFINITION,
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ children: [ // dd
+ { role: ROLE_TEXT_LEAF }
+ ]
+ }
+ ]
+ };
+ testElm("dl", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:del contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "text-line-through-style": "solid" }
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF } // HTML:del text
+ ]
+ };
+ testElm("del_container", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:details with open state
+
+ obj = {
+ role: ROLE_DETAILS,
+ children: [
+ {
+ role: ROLE_SUMMARY,
+ states: STATE_EXPANDED,
+ actions: "collapse"
+ },
+ { role: ROLE_PARAGRAPH }
+ ]
+ };
+ testElm("details", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:details with closed (default) state
+
+ obj = {
+ role: ROLE_DETAILS,
+ children: [
+ {
+ role: ROLE_SUMMARY,
+ states: STATE_COLLAPSED,
+ actions: "expand"
+ }
+ ]
+ };
+ testElm("details_closed", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:dfn contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { "font-style": "italic" },
+ 12: { }
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // HTML:dfn text
+ { role: ROLE_TEXT_LEAF } // plain text
+ ]
+ };
+ testElm("dfn_container", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:dialog
+
+ todo(isAccessible("dialog"), "dialog element is not accessible");
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:div
+
+ obj = {
+ role: ROLE_SECTION,
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ children: [
+ { role: ROLE_TEXT_LEAF } // plain text
+ ]
+ };
+ testElm("div", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:em in a paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "font-style": "italic" }
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF } // HTML:em text
+ ]
+ };
+ testElm("em_container", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:embed (windowless and windowed plugins)
+
+ if (WIN) {
+ obj = {
+ role: ROLE_EMBEDDED_OBJECT,
+ states: STATE_UNAVAILABLE
+ };
+
+ testElm("embed_plugin_windowless", obj);
+
+ obj = {
+ role: ROLE_EMBEDDED_OBJECT,
+ absentStates: STATE_UNAVAILABLE
+ };
+ testElm("embed_plugin_windowed", obj);
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:fieldset and HTML:legend
+
+ obj = {
+ role: ROLE_GROUPING,
+ relations: {
+ RELATION_LABELLED_BY: "legend"
+ },
+ children: [
+ {
+ role: ROLE_LABEL,
+ relations: {
+ RELATION_LABEL_FOR: "fieldset"
+ },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
+ },
+ {
+ role: ROLE_ENTRY
+ }
+ ]
+ };
+ testElm("fieldset", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:figure and HTML:figcaption
+
+ obj = {
+ role: ROLE_FIGURE,
+ attributes: { "xml-roles": "figure" },
+ relations: {
+ RELATION_LABELLED_BY: "figcaption"
+ },
+ children: [
+ { role: ROLE_GRAPHIC },
+ {
+ role: ROLE_CAPTION,
+ relations: {
+ RELATION_LABEL_FOR: "figure"
+ },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
+ }
+ ]
+ };
+ testElm("figure", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:footer
+
+ obj = {
+ role: ROLE_FOOTER,
+ attributes: { "xml-roles": "contentinfo" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
+ };
+ testElm("footer", obj);
+
+ obj = {
+ role: ROLE_FOOTER,
+ absentAttributes: { "xml-roles": "contentinfo" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
+ };
+ testElm("footer_in_article", obj);
+ testElm("footer_in_section", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:form
+
+ obj = {
+ role: ROLE_FORM
+ };
+ testElm("form", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // // HTML:frameset, HTML:frame and HTML:iframe
+
+ obj = {
+ INTERNAL_FRAME: [ { // HTML:iframe
+ DOCUMENT: [ {
+ INTERNAL_FRAME: [ { // HTML:frame
+ DOCUMENT: [ { role: ROLE_TEXT_LEAF} ]
+ } ]
+ } ]
+ } ]
+ };
+ testElm("frameset_container", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:h1, HTML:h2, HTML:h3, HTML:h4, HTML:h5, HTML:h6
+
+ obj = {
+ role: ROLE_HEADING,
+ attributes: { "level": "1" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
+ };
+ testElm("h1", obj);
+
+ obj = {
+ role: ROLE_HEADING,
+ attributes: { "level": "2" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
+ };
+ testElm("h2", obj);
+
+ obj = {
+ role: ROLE_HEADING,
+ attributes: { "level": "3" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
+ };
+ testElm("h3", obj);
+
+ obj = {
+ role: ROLE_HEADING,
+ attributes: { "level": "4" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
+ };
+ testElm("h4", obj);
+
+ obj = {
+ role: ROLE_HEADING,
+ attributes: { "level": "5" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
+ };
+ testElm("h5", obj);
+
+ obj = {
+ role: ROLE_HEADING,
+ attributes: { "level": "6" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
+ };
+ testElm("h6", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:header
+
+ obj = {
+ role: ROLE_HEADER,
+ attributes: { "xml-roles": "banner" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
+ };
+ testElm("header", obj);
+
+ obj = {
+ role: ROLE_HEADER,
+ absentAttributes: { "xml-roles": "banner" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
+ };
+ testElm("header_in_article", obj);
+ testElm("header_in_section", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:hr
+
+ obj = {
+ role: ROLE_SEPARATOR,
+ };
+ testElm("hr", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ obj = { // HTML:i contained by paragraph
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "font-style": "italic" }
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF } // HTML:i text
+ ]
+ }
+ testElm("i_container", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:img
+
+ obj = {
+ role: ROLE_GRAPHIC,
+ interfaces: [ nsIAccessibleImage ]
+ };
+ testElm("img", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:input@type="button"
+
+ obj = {
+ role: ROLE_PUSHBUTTON,
+ absentStates: STATE_DEFAULT
+ };
+ testElm("input_button", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:input@type="checkbox"
+
+ obj = {
+ role: ROLE_CHECKBUTTON,
+ states: STATE_CHECKABLE,
+ absentStates: STATE_CHECKED,
+ actions: "check"
+ };
+ testElm("input_checkbox", obj);
+
+ obj = {
+ role: ROLE_CHECKBUTTON,
+ states: STATE_CHECKABLE | STATE_CHECKED,
+ actions: "uncheck"
+ };
+ testElm("input_checkbox_true", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:input@type="file"
+
+ obj = {
+ TEXT_CONTAINER: [
+ { role: ROLE_PUSHBUTTON },
+ { role: ROLE_LABEL }
+ ]
+ };
+ testElm("input_file", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:input@type="image"
+
+ obj = {
+ role: ROLE_PUSHBUTTON,
+ absentStates: STATE_DEFAULT,
+ actions: "press"
+ };
+ testElm("input_image", obj);
+ testElm("input_submit", obj);
+
+ obj = {
+ role: ROLE_PUSHBUTTON,
+ actions: "press",
+ states: STATE_DEFAULT
+ };
+ testElm("input_image_default", obj);
+ testElm("input_submit_default", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:input@type="number" and etc
+
+ obj = {
+ role: ROLE_SPINBUTTON,
+ interfaces: [ nsIAccessibleValue ],
+ children: [
+ {
+ role: ROLE_ENTRY,
+ extraStates: EXT_STATE_EDITABLE | EXT_STATE_SINGLE_LINE,
+ actions: "activate",
+ interfaces: [ nsIAccessibleText, nsIAccessibleEditableText ],
+ children: [
+ { role: ROLE_TEXT_LEAF }
+ ]
+ },
+ {
+ role: ROLE_PUSHBUTTON,
+ actions: "press"
+ },
+ {
+ role: ROLE_PUSHBUTTON,
+ actions: "press"
+ }
+ ]
+ };
+ testElm("input_number", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:input@type="text" and etc
+
+ obj = {
+ role: ROLE_ENTRY,
+ extraStates: EXT_STATE_EDITABLE | EXT_STATE_SINGLE_LINE,
+ actions: "activate",
+ interfaces: [ nsIAccessibleText, nsIAccessibleEditableText ],
+ children: [
+ { role: ROLE_TEXT_LEAF }
+ ]
+ };
+ testElm("input_email", obj);
+ testElm("input_search", obj);
+ testElm("input_tel", obj);
+ testElm("input_text", obj);
+ testElm("input_url", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:input@type="password"
+
+ obj = {
+ role: ROLE_PASSWORD_TEXT,
+ states: STATE_PROTECTED,
+ extraStates: EXT_STATE_EDITABLE,
+ actions: "activate",
+ children: [
+ {
+ role: ROLE_TEXT_LEAF
+ }
+ ]
+ };
+ testElm("input_password", obj);
+ ok(getAccessible("input_password").firstChild.name != "44",
+ "text leaf for password shouldn't have its real value as its name!");
+
+ obj = {
+ role: ROLE_PASSWORD_TEXT,
+ states: STATE_PROTECTED | STATE_READONLY,
+ actions: "activate",
+ children: [
+ {
+ role: ROLE_TEXT_LEAF
+ }
+ ]
+ };
+ testElm("input_password_readonly", obj);
+ ok(getAccessible("input_password_readonly").firstChild.name != "44",
+ "text leaf for password shouldn't have its real value as its name!");
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:input@type="radio"
+
+ obj = {
+ role: ROLE_RADIOBUTTON,
+ states: STATE_CHECKABLE,
+ absentStates: STATE_CHECKED,
+ actions: "select"
+ };
+ testElm("input_radio", obj);
+
+ obj = {
+ role: ROLE_RADIOBUTTON,
+ states: STATE_CHECKABLE | STATE_CHECKED,
+ actions: "select"
+ };
+ testElm("input_radio_true", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:input@type="range"
+
+ obj = {
+ role: ROLE_SLIDER
+ };
+ testElm("input_range", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:input@type="reset"
+
+ obj = {
+ role: ROLE_PUSHBUTTON,
+ actions: "press",
+ absentStates: STATE_DEFAULT
+ };
+ testElm("input_reset", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:ins contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "text-underline-style": "solid" }
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF } // HTML:ins text
+ ]
+ };
+ testElm("ins_container", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:kbd contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "font-family": kMonospaceFontFamily }
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF } // HTML:kbd text
+ ]
+ };
+ testElm("kbd_container", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:keygen
+
+ obj = {
+ role: ROLE_COMBOBOX,
+ states: STATE_COLLAPSED | STATE_HASPOPUP,
+ extraStates: EXT_STATE_EXPANDABLE,
+ actions: "open",
+ children: [
+ { COMBOBOX_LIST: [
+ { role: ROLE_COMBOBOX_OPTION }, // high grade
+ { role: ROLE_COMBOBOX_OPTION } // medium grade
+ ] }
+ ]
+ };
+ testElm("keygen", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:label
+
+ obj = {
+ role: ROLE_LABEL,
+ todo_relations: {
+ RELATION_LABEL_FOR: "label_input"
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ {
+ role: ROLE_ENTRY,
+ relations: {
+ RELATION_LABELLED_BY: "label"
+ }
+ }
+ ]
+ };
+ testElm("label", obj);
+
+ obj = {
+ role: ROLE_LABEL,
+ relations: {
+ RELATION_LABEL_FOR: "label_for_input"
+ }
+ };
+ testElm("label_for", obj);
+
+ obj = {
+ role: ROLE_ENTRY,
+ relations: {
+ RELATION_LABELLED_BY: "label_for"
+ }
+ };
+ testElm("label_for_input", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:ul, HTML:ol, HTML:li
+
+ obj = { // ul or ol
+ role: ROLE_LIST,
+ states: STATE_READONLY,
+ children: [
+ { // li
+ role: ROLE_LISTITEM,
+ states: STATE_READONLY,
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
+ }
+ ]
+ };
+ testElm("ul", obj);
+ testElm("ol", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:link
+
+ ok(!isAccessible("link"), "link element is not accessible");
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:main
+
+ obj = {
+ todo_role: ROLE_GROUPING,
+ attributes: { "xml-roles": "main" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
+ };
+ testElm("main", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:map
+
+ ok(!isAccessible("map_imagemap"),
+ "map element is not accessible if used as an image map");
+
+ obj = {
+ role: ROLE_TEXT_CONTAINER
+ };
+ testElm("map", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:mark contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "background-color": "rgb(255, 255, 0)" }
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF } // HTML:mark text
+ ]
+ };
+ testElm("mark_container", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:math
+
+ obj = {
+ role: ROLE_MATHML_MATH
+ };
+ testElm("math", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:menu
+
+ obj = {
+ todo_role: ROLE_MENUPOPUP
+ };
+ testElm("menu", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:meter
+
+ todo(isAccessible("meter"), "meter element is not accessible");
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:nav
+
+ obj = {
+ role: ROLE_SECTION,
+ attributes: { "xml-roles": "navigation" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
+ };
+ testElm("nav", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:object (windowless and windowed plugins) and HTML:param
+
+ if (WIN) {
+ obj = {
+ role: ROLE_EMBEDDED_OBJECT,
+ states: STATE_UNAVAILABLE,
+ children: [ ] // no child for HTML:param
+ };
+ testElm("object_plugin_windowless", obj);
+
+ obj = {
+ role: ROLE_EMBEDDED_OBJECT,
+ absentStates: STATE_UNAVAILABLE
+ };
+ testElm("object_plugin_windowed", obj);
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:select, HTML:optgroup and HTML:option
+
+ obj = { // HMTL:select@size > 1
+ role: ROLE_LISTBOX,
+ states: STATE_FOCUSABLE,
+ absentStates: STATE_MULTISELECTABLE,
+ interfaces: [ nsIAccessibleSelectable ],
+ children: [
+ { GROUPING: [ // HTML:optgroup
+ { role: ROLE_STATICTEXT },
+ { role: ROLE_OPTION }, // HTML:option
+ { role: ROLE_OPTION }
+ ] },
+ {
+ role: ROLE_OPTION,
+ states: STATE_FOCUSABLE,
+ actions: "select",
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
+ }
+ ]
+ };
+ testElm("select_listbox", obj);
+
+ obj = { // HTML:select@multiple
+ role: ROLE_LISTBOX,
+ states: STATE_FOCUSABLE | STATE_MULTISELECTABLE,
+ children: [
+ { role: ROLE_OPTION },
+ { role: ROLE_OPTION },
+ { role: ROLE_OPTION }
+ ]
+ };
+ testElm("select_listbox_multiselectable", obj);
+
+ obj = { // HTML:select
+ role: ROLE_COMBOBOX,
+ states: STATE_FOCUSABLE,
+ children: [
+ {
+ role: ROLE_COMBOBOX_LIST,
+ children: [
+ { role: ROLE_COMBOBOX_OPTION },
+ { role: ROLE_COMBOBOX_OPTION },
+ { role: ROLE_COMBOBOX_OPTION }
+ ]
+ }
+ ]
+ };
+ testElm("select_combobox", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:output
+
+ obj = {
+ role: ROLE_SECTION,
+ attributes: { "live": "polite" },
+ todo_relations: {
+ RELATION_CONTROLLED_BY: "output_input"
+ },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
+ };
+ testElm("output", obj);
+
+ obj = {
+ role: ROLE_ENTRY,
+ relations: {
+ RELATION_CONTROLLER_FOR: "output"
+ }
+ };
+ testElm("output_input", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:pre
+
+ obj = {
+ role: ROLE_TEXT_CONTAINER,
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
+ };
+ testElm("pre", obj);
+
+ ///////////////////////////////////////////////////////////////////////////
+ // HTML:progress
+
+ obj = {
+ role: ROLE_PROGRESSBAR,
+ absentStates: STATE_MIXED,
+ interfaces: [ nsIAccessibleValue ]
+ };
+ testElm("progress", obj);
+
+ obj = {
+ role: ROLE_PROGRESSBAR,
+ states: STATE_MIXED
+ };
+ testElm("progress_indeterminate", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:q
+
+ obj = {
+ role: ROLE_TEXT,
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ],
+ children: [
+ { role: ROLE_STATICTEXT }, // left quote
+ { role: ROLE_TEXT_LEAF }, // quoted text
+ { role: ROLE_STATICTEXT } // right quote
+ ]
+ };
+ testElm("q", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:ruby
+
+ todo(isAccessible("ruby"), "ruby element is not accessible");
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:s contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "text-line-through-style": "solid" }
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF } // HTML:i text
+ ]
+ };
+ testElm("s_container", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:samp contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF } // HTML:samp text
+ ]
+ };
+ testElm("samp_container", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:section
+
+ obj = {
+ role: ROLE_SECTION,
+ attributes: { "xml-roles": "region" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
+ };
+ testElm("section", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:small contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "font-size": "10pt" }
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF } // HTML:small text
+ ]
+ };
+ testElm("small_container", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:source
+
+ ok(!isAccessible("source"), "source element is not accessible");
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:span
+
+ ok(!isAccessible("span"), "span element is not accessible");
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:strong contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF } // HTML:strong text
+ ]
+ };
+ testElm("strong_container", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:sub contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "text-position": "sub" }
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF } // HTML:sub text
+ ]
+ };
+ testElm("sub_container", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:sup contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "text-position": "super" }
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF } // HTML:sup text
+ ]
+ };
+ testElm("sup_container", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:svg
+
+ obj = {
+ todo_role: ROLE_GRAPHIC
+ };
+ testElm("svg", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:textarea
+
+ obj = {
+ role: ROLE_ENTRY,
+ extraStates: EXT_STATE_MULTI_LINE | EXT_STATE_EDITABLE,
+ actions: "activate",
+ interfaces: [ nsIAccessibleText, nsIAccessibleEditableText ]
+ };
+ testElm("textarea", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:time
+
+ obj = {
+ role: ROLE_TEXT,
+ attributes: { "xml-roles": "time", "datetime": "2001-05-15 19:00" },
+ interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ]
+ };
+ testElm("time", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:u contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ textAttrs: {
+ 0: { },
+ 6: { "text-underline-style" : "solid" }
+ },
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF } // HTML:u text
+ ]
+ };
+ testElm("u_container", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML:var contained by paragraph
+
+ obj = {
+ role: ROLE_PARAGRAPH,
+ children: [
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF }, // HTML:var text
+ { role: ROLE_TEXT_LEAF }, // plain text
+ { role: ROLE_TEXT_LEAF } // HTML:var text
+ ]
+ };
+ testElm("var_container", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ obj = { // HTML:video
+ role: ROLE_GROUPING
+ };
+ testElm("video", obj);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
+
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Implement figure and figcaption accessibility"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=658272">
+ Mozilla Bug 658272
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <a id="a_href" href="www.mozilla.com">mozilla site</a>
+ <a id="a_nohref">anchor</a>
+ <table>
+ <tr>
+ <td id="td_abbr"><abbr title="World Wide Web">WWW</abbr></td>
+ </tr>
+ </table>
+ <address id="address">
+ Mozilla Foundation<br>
+ 1981 Landings Drive<br>
+ Building K<br>
+ Mountain View, CA 94043-0801<br>
+ USA
+ </address>
+
+ <map name="atoz_map">
+ <area id="area_href"
+ href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b"
+ coords="17,0,30,14" alt="b" shape="rect">
+ <area id="area_nohref"
+ coords="0,0,13,14" alt="a" shape="rect">
+ </map>
+ <img id="imgmap" width="447" height="15"
+ usemap="#atoz_map"
+ src="../letters.gif">
+
+ <article id="article">A document</article>
+ <audio id="audio" controls="true">
+ <source id="source" src="../bug461281.ogg" type="video/ogg">
+ </audio>
+
+ <aside id="aside">
+ <p>Some content related to an &lt;article&gt;</p>
+ </aside>
+
+ <p id="b_container">normal<b>bold</b></p>
+ <p id="bdi_container">User <bdi>إيان</bdi>: 90 points</p>
+ <p id="bdo_container"><bdo dir="rtl">This text will go right to left.</bdo></p>
+
+ <blockquote id="blockquote" cite="http://developer.mozilla.org">
+ <p>This is a quotation taken from the Mozilla Developer Center.</p>
+ </blockquote>
+
+ <!-- two BRs, one will be eaten -->
+ <p id="br_container"><br><br></p>
+
+ <button id="button">button</button>
+ <form>
+ <button id="button_default" type="submit">button</button>
+ </form>
+
+ <canvas id="canvas"></canvas>
+
+ <table id="table">
+ <caption id="caption">caption</caption>
+ <thead>
+ <tr>
+ <th>col1</th><th>col2</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <th>col1</th><td>cell2</td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td>cell5</td><td>cell6</td>
+ </tr>
+ </tfoot>
+ </table>
+
+ <p id="cite_container">normal<cite>cite</cite></p>
+ <p id="code_container">normal<code>code</code></p>
+
+ <table id="colNcolgroup_table">
+ <colgroup>
+ <col>
+ <col span="2">
+ </colgroup>
+ <tr>
+ <td>Lime</td>
+ <td>Lemon</td>
+ <td>Orange</td>
+ </tr>
+ </table>
+
+ <p id="data_container"><data value="8">Eight</data></p>
+
+ <datalist id="datalist">
+ <summary id="summary">details</summary>
+ <option>Paris</option>
+ <option>San Francisco</option>
+ </datalist>
+ <input id="autocomplete_datalist" list="datalist">
+
+ <dl id="dl">
+ <dt>item1</dt><dd>description</dd>
+ </dl>
+
+ <p id="del_container">normal<del>Removed</del></p>
+
+ <details id="details" open="open">
+ <summary>Information</summary>
+ <p>If your browser supports this element, it should allow you to expand and collapse these details.</p>
+ </details>
+
+ <details id="details_closed">
+ <summary>Information</summary>
+ <p>If your browser supports this element, it should allow you to expand and collapse these details.</p>
+ </details>
+
+ <p id="dfn_container"><dfn id="def-internet">The Internet</dfn> is a global
+ system of interconnected networks that use the Internet Protocol Suite (TCP/IP)
+ to serve billions of users worldwide.</p>
+
+ <dialog id="dialog" open="true">This is a dialog</dialog>
+
+ <div id="div">div</div>
+
+ <p id="em_container">normal<em>emphasis</em></p>
+
+ <embed id="embed_plugin_windowless" type="application/x-test"
+ width="300" height="300"></embed>
+ <embed id="embed_plugin_windowed" type="application/x-test" wmode="window"
+ width="300" height="300"></embed>
+
+ <fieldset id="fieldset">
+ <legend id="legend">legend</legend>
+ <input />
+ </fieldset>
+
+ <figure id="figure">
+ <img src="../moz.png" alt="An awesome picture">
+ <figcaption id="figcaption">Caption for the awesome picture</figcaption>
+ </figure>
+
+ <footer id="footer">Some copyright info</footer>
+ <article>
+ <footer id="footer_in_article">Some copyright info</footer>
+ </article>
+ <section>
+ <footer id="footer_in_section">Some copyright info</footer>
+ </section>
+
+ <form id="form"></form>
+
+ <iframe id="frameset_container"
+ src="data:text/html,<html><frameset><frame src='data:text/html,hi'></frame></frameset></html>">
+ </iframe>
+
+ <h1 id="h1">heading1</h1>
+ <h2 id="h2">heading2</h2>
+ <h3 id="h3">heading3</h3>
+ <h4 id="h4">heading4</h4>
+ <h5 id="h5">heading5</h5>
+ <h6 id="h6">heading6</h6>
+
+ <header id="header">A logo</header>
+ <article>
+ <header id="header_in_article">Not logo</header>
+ </article>
+ <section>
+ <header id="header_in_section">Not logo</header>
+ </section>
+
+ <hr id="hr">
+ <p id="i_container">normal<i>italic</i></p>
+ <img id="img" src="../moz.png">
+
+ <input id="input_button" type="button" value="Button">
+ <input id="input_checkbox" type="checkbox">
+ <input id="input_checkbox_true" type="checkbox" checked>
+ <input id="input_file" type="file">
+ <input id="input_image" type="image">
+ <form>
+ <input id="input_image_default" type="image">
+ </form>
+ <input id="input_submit" type="submit">
+ <form>
+ <input id="input_submit_default" type="submit">
+ </form>
+ <input id="input_number" type="number" value="44">
+ <input id="input_text" type="text" value="hi">
+ <input id="input_search" type="search" value="cats">
+ <input id="input_email" type="email" value="me@mozilla.com">
+ <input id="input_tel" type="tel" value="111.111.1111">
+ <input id="input_url" type="url" value="www.mozilla.com">
+ <input id="input_password" type="password" value="44">
+ <input id="input_password_readonly" type="password" value="44" readonly>
+ <input id="input_radio" type="radio">
+ <input id="input_radio_true" type="radio" checked>
+ <input id="input_range" type="range">
+ <form>
+ <input id="input_reset" type="reset">
+ </form>
+
+ <p id="ins_container">normal<ins>Inserted</ins></p>
+ <p id="kbd_container">normal<kbd>cmd</kbd></p>
+ <keygen id="keygen" name="RSA public key" challenge="123456789" keytype="RSA">
+
+ <label id="label">label<input id="label_input"></label>
+ <label id="label_for" for="label_for_input">label</label>
+ <input id="label_for_input">
+
+ <ul id="ul">
+ <li>item1</li>
+ </ul>
+ <ol id="ol">
+ <li>item1</li>
+ </ol>
+
+ <main id="main">main</main>
+
+ <map id="map_imagemap" name="atoz_map">
+ <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b"
+ coords="17,0,30,14" alt="b" shape="rect">
+ <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#a"
+ coords="0,0,13,14" alt="a" shape="rect">
+ </map>
+ <img id="imgmap" width="447" height="15"
+ usemap="#atoz_map"
+ src="../letters.gif">
+
+ <map id="map" title="Navigation Bar" name="mapgroup">
+ <p>
+ [<a href="#how">Bypass navigation bar</a>]
+ [<a href="home.html">Home</a>]
+ </p>
+ </map>
+
+ <p id="mark_container">normal<mark>highlighted</mark></p>
+
+ <math id="math">
+ <mrow>
+ <mrow>
+ <msup>
+ <mi>a</mi>
+ <mn>2</mn>
+ </msup>
+ <mo>+</mo>
+ <msup>
+ <mi>b</mi>
+ <mn>2</mn>
+ </msup>
+ </mrow>
+ <mo>=</mo>
+ <msup>
+ <mi>c</mi>
+ <mn>2</mn>
+ </msup>
+ </mrow>
+ </math>
+
+ <menu id="menu" type="toolbar">
+ <li>
+ <menu label="File">
+ <button type="button" onclick="new()">New...</button>
+ <button type="button" onclick="save()">Save...</button>
+ </menu>
+ </li>
+ <li>
+ <menu label="Edit">
+ <button type="button" onclick="cut()">Cut...</button>
+ <button type="button" onclick="copy()">Copy...</button>
+ <button type="button" onclick="paste()">Paste...</button>
+ </menu>
+ </li>
+ </menu>
+
+ <meter id="meter" min="0" max="1000" low="300" high="700" value="200">200 Euro</meter>
+
+ <nav id="nav">
+ <ul>
+ <li><a href="#">Home</a></li>
+ <li><a href="#">About</a></li>
+ <li><a href="#">Contact</a></li>
+ </ul>
+ </nav>
+
+ <object id="object_plugin_windowless" type="application/x-test"
+ width="300" height="300">
+ <param name="foo" value="bar">
+ </object>
+ <object id="object_plugin_windowed" type="application/x-test" wmode="window"
+ width="300" height="300"></object>
+
+ <select id="select_listbox" size="4">
+ <optgroup label="Colors">
+ <option>Red</option>
+ <option>Blue</option>
+ </optgroup>
+ <option>Animal</option>
+ </select>
+
+ <select id="select_listbox_multiselectable" multiple>
+ <option>Red</option>
+ <option>Blue</option>
+ <option>Green</option>
+ </select>
+
+ <select id="select_combobox">
+ <option>Red</option>
+ <option>Blue</option>
+ <option>Green</option>
+ </select>
+
+ <input id="output_input">
+ <output id="output" for="output_input"></output>
+
+ <pre id="pre">pre</pre>
+
+ <progress id="progress" min="0" value="21" max="42"></progress>
+ <progress id="progress_indeterminate"></progress>
+
+ <q id="q" cite="http://en.wikipedia.org/wiki/Kenny_McCormick#Cultural_impact">
+ Oh my God, they killed Kenny!
+ </q>
+
+ <ruby id="ruby">
+ 漢 <rp>(</rp><rt>Kan</rt><rp>)</rp>
+ 字 <rp>(</rp><rt>ji</rt><rp>)</rp>
+ </ruby>
+
+ <p id="s_container">normal<s>striked</s></p>
+ <p id="samp_container">normal<samp>sample</samp></p>
+ <section id="section">section</section>
+ <p id="small_container">normal<small>small</small></p>
+ <span id="span"></span>
+ <p id="strong_container">normal<strong>strong</strong></p>
+ <p id="sub_container">normal<sub>sub</sub></p>
+ <p id="sup_container">normal<sup>sup</sup></p>
+
+ <svg id="svg"></svg>
+ <textarea id="textarea"></textarea>
+
+ <p>The concert took place on <time id="time" datetime="2001-05-15 19:00">May 15</time></p>
+ <p id="u_container">normal<u>underline</u></p>
+ <p id="var_container">An equation: <var>x</var> = <var>y</var></p>
+
+ <video id="video" controls="true">
+ <source id="source" src="../bug461281.ogg" type="video/ogg">
+ </video>
+
+</video>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/elm/test_MathMLSpec.html b/accessible/tests/mochitest/elm/test_MathMLSpec.html
new file mode 100644
index 000000000..80f1e9e70
--- /dev/null
+++ b/accessible/tests/mochitest/elm/test_MathMLSpec.html
@@ -0,0 +1,620 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML a11y spec tests</title>
+ <link id="link" rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../actions.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+ <script type="application/javascript"
+ src="../relations.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // math
+
+ obj = {
+ role: ROLE_MATHML_MATH,
+ };
+ testElm("math", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // mi
+
+ obj = {
+ role: ROLE_MATHML_IDENTIFIER,
+ };
+ testElm("mi", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // mn
+
+ obj = {
+ role: ROLE_MATHML_NUMBER,
+ };
+ testElm("mn", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // mo
+
+ obj = {
+ role: ROLE_MATHML_OPERATOR,
+ attributes: { accent: "true", largeop: "true" }
+ };
+ testElm("mo", obj);
+
+ obj = {
+ role: ROLE_MATHML_OPERATOR,
+ attributes: { fence: "true" }
+ };
+ testElm("mo_fence", obj);
+
+ obj = {
+ role: ROLE_MATHML_OPERATOR,
+ attributes: { separator: "true" }
+ };
+ testElm("mo_separator", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // mtext
+
+ obj = {
+ role: ROLE_MATHML_TEXT,
+ };
+ testElm("mtext", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // ms
+
+ obj = {
+ role: ROLE_MATHML_STRING_LITERAL,
+ };
+ testElm("ms", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // mglyph
+
+ obj = {
+ role: ROLE_MATHML_GLYPH,
+ };
+ testElm("mglyph", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // mrow
+
+ obj = {
+ role: ROLE_MATHML_ROW,
+ };
+ testElm("mrow", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // mfrac
+
+ obj = {
+ role: ROLE_MATHML_FRACTION,
+ attributes: { bevelled: "true", linethickness: "thick" }
+ };
+ testElm("mfrac", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // msqrt
+
+ obj = {
+ role: ROLE_MATHML_SQUARE_ROOT,
+ };
+ testElm("msqrt", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // mroot
+
+ obj = {
+ role: ROLE_MATHML_ROOT,
+ relations: {
+ RELATION_NODE_PARENT_OF: ["mroot_index", "mroot_base"]
+ },
+ children: [
+ {
+ role: ROLE_MATHML_IDENTIFIER,
+ relations: { RELATION_NODE_CHILD_OF: "mroot" }
+ },
+ {
+ role: ROLE_MATHML_NUMBER,
+ relations: { RELATION_NODE_CHILD_OF: "mroot" }
+ }
+ ]
+ };
+ testElm("mroot", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // mfenced
+
+ obj = {
+ role: ROLE_MATHML_FENCED,
+ attributes: { open: "]", close: "[", separators: "." }
+ };
+ testElm("mfenced", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // menclose
+
+ obj = {
+ role: ROLE_MATHML_ENCLOSED,
+ attributes: { notation: "circle" }
+ };
+ testElm("menclose", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // mstyle, mpadded, mphantom
+
+ obj = {
+ role: ROLE_MATHML_STYLE,
+ };
+ testElm("mstyle", obj);
+
+ ok(!isAccessible("mpadded"), "mpadded should not have accessible");
+ ok(!isAccessible("mphantom"), "mphantom should not have accessible");
+
+ //////////////////////////////////////////////////////////////////////////
+ // msub
+
+ obj = {
+ role: ROLE_MATHML_SUB,
+ };
+ testElm("msub", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // msup
+
+ obj = {
+ role: ROLE_MATHML_SUP,
+ };
+ testElm("msup", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // msubsup
+
+ obj = {
+ role: ROLE_MATHML_SUB_SUP,
+ };
+ testElm("msubsup", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // munder
+
+ obj = {
+ role: ROLE_MATHML_UNDER,
+ attributes: { accentunder: "true", align: "center" }
+ };
+ testElm("munder", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // mover
+
+ obj = {
+ role: ROLE_MATHML_OVER,
+ attributes: { accent: "true", align: "center" }
+ };
+ testElm("mover", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // munderover
+
+ obj = {
+ role: ROLE_MATHML_UNDER_OVER,
+ attributes: { accent: "true", accentunder: "true", align: "center" },
+ };
+ testElm("munderover", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // mmultiscripts
+
+ obj = {
+ role: ROLE_MATHML_MULTISCRIPTS,
+ };
+ testElm("mmultiscripts", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // mtable
+
+ obj = {
+ role: ROLE_MATHML_TABLE,
+ attributes: { align: "center", columnlines: "solid", rowlines: "solid" }
+ };
+ testElm("mtable", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // mlabeledtr
+
+ obj = {
+ role: ROLE_MATHML_LABELED_ROW,
+ };
+ testElm("mlabeledtr", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // mtr
+
+ obj = {
+ role: ROLE_MATHML_TABLE_ROW,
+ };
+ testElm("mtr", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // mtd
+
+ obj = {
+ role: ROLE_MATHML_CELL,
+ };
+ testElm("mtd", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // maction
+
+ obj = {
+ role: ROLE_MATHML_ACTION,
+ attributes: { actiontype: "toggle", selection: "1" }
+ };
+ testElm("maction", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // merror
+
+ obj = {
+ role: ROLE_MATHML_ERROR,
+ };
+ testElm("merror", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // semantics, annotation, annotation-xml
+ ok(!isAccessible("semantics"), "semantics should not have accessible");
+ ok(!isAccessible("annotation"), "annotation should not have accessible");
+ ok(!isAccessible("annotation-xml"), "annotation-xml should not have accessible");
+
+ //////////////////////////////////////////////////////////////////////////
+ // mstack
+
+ obj = {
+ role: ROLE_MATHML_STACK,
+ attributes: { align: "center" }
+ };
+ testElm("mstack", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // mlongdiv
+
+ obj = {
+ role: ROLE_MATHML_LONG_DIVISION,
+ attributes: { longdivstyle: "stackedrightright" }
+ };
+ testElm("mlongdiv", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // msgroup
+
+ obj = {
+ role: ROLE_MATHML_STACK_GROUP,
+ attributes: { position: "2", shift: "-1" }
+ };
+ testElm("msgroup", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // msrow
+
+ obj = {
+ role: ROLE_MATHML_STACK_ROW,
+ attributes: { position: "1" }
+ };
+ testElm("msrow", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // mscarries
+
+ obj = {
+ role: ROLE_MATHML_STACK_CARRIES,
+ attributes: { location: "nw", position: "1" }
+ };
+ testElm("mscarries", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // mscarry
+
+ obj = {
+ role: ROLE_MATHML_STACK_CARRY,
+ attributes: { crossout: "updiagonalstrike" }
+ };
+ testElm("mscarry", obj);
+
+ //////////////////////////////////////////////////////////////////////////
+ // msline
+
+ obj = {
+ role: ROLE_MATHML_STACK_LINE,
+ attributes: { position: "1" }
+ };
+ testElm("msline", obj);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
+
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Implement figure and figcaption accessibility"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=658272">
+ Mozilla Bug 658272
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <math id="math">
+ <mrow id="mrow">
+ <mrow>
+ <msup id="msup">
+ <mi id="mi">a</mi>
+ <mn id="mn">2</mn>
+ </msup>
+ <mo id="mo" accent="true" largeop="true">+</mo>
+ <msqrt id="msqrt">
+ <mn>2</mn>
+ </msqrt>
+ </mrow>
+ <mo>=</mo>
+ <msub id="msub">
+ <mi>c</mi>
+ <mn>2</mn>
+ </msub>
+ </mrow>
+ <mspace id="mspace" width="1em"/>
+ <mtext id="mtext">Arbitrary text</mtext>
+ <mspace width="1em"/>
+ <ms id="ms">InterpretedStringLiteral</ms>
+ <mi>
+ <mglyph id="mglyph" src="../letters.gif" alt="letters"/>
+ </mi>
+ <mfrac id="mfrac" bevelled="true" linethickness="thick">
+ <mi>x</mi>
+ <mn>2</mn>
+ </mfrac>
+ <mroot id="mroot">
+ <mi id="mroot_base">x</mi>
+ <mn id="mroot_index">5</mn>
+ </mroot>
+ <mspace width="1em"/>
+ <mfenced id="mfenced" close="[" open="]" separators=".">
+ <mrow>
+ <mi>x</mi>
+ <mi>y</mi>
+ </mrow>
+ </mfenced>
+ <mrow>
+ <mo id="mo_fence" fence="true">[</mo>
+ <mrow>
+ X
+ <mo id="mo_separator" separator="true">,</mo>
+ Y
+ </mrow>
+ <mo fence="true"> closing-fence </mo>
+ </mrow>
+ <mspace width="1em"/>
+ <menclose id="menclose" notation="circle">
+ <mi>a</mi>
+ <mo>+</mo>
+ <mi>b</mi>
+ </menclose>
+ <mstyle id="mstyle" dir="rtl" mathcolor="blue">
+ <mpadded id="mpadded" height="100px" width="200px">
+ <mi>x</mi>
+ <mphantom id="mphantom">
+ <mo>+</mo>
+ <mi>y</mi>
+ </mphantom>
+ </mpadded>
+ </mstyle>
+
+ <msubsup id="msubsup">
+ <mi>b</mi>
+ <mn>1</mn>
+ <mn>2</mn>
+ </msubsup>
+ <munder id="munder" accentunder="true" align="center">
+ <mrow>
+ <mi> x </mi>
+ <mo> + </mo>
+ <mi> y </mi>
+ <mo> + </mo>
+ <mi> z </mi>
+ </mrow>
+ <mo> &#x23DF;<!--BOTTOM CURLY BRACKET--> </mo>
+ </munder>
+ <mspace width="1em"/>
+ <mover id="mover" accent="true" align="center">
+ <mi> x </mi>
+ <mo> &#x5E;<!--CIRCUMFLEX ACCENT--> </mo>
+ </mover>
+ <munderover id="munderover" accentunder="true" accent="true" align="center">
+ <mo> &#x222B;<!--INTEGRAL--> </mo>
+ <mn> 0 </mn>
+ <mi> &#x221E;<!--INFINITY--> </mi>
+ </munderover>
+ <mmultiscripts id="mmultiscripts">
+ <mi> R </mi>
+ <mi> i </mi>
+ <none/>
+ <none/>
+ <mi> j </mi>
+ <mi> k </mi>
+ <none/>
+ <mi> l </mi>
+ <none/>
+ </mmultiscripts>
+
+ <mtable id="mtable" align="center" columnlines="solid" rowlines="solid">
+ <mlabeledtr id="mlabeledtr">
+ <mtd>
+ <mtext> (2.1) </mtext>
+ </mtd>
+ <mtd>
+ <mrow>
+ <mi>E</mi>
+ <mo>=</mo>
+ <mrow>
+ <mi>m</mi>
+ <mo>&#x2062;<!--INVISIBLE TIMES--></mo>
+ <msup>
+ <mi>c</mi>
+ <mn>2</mn>
+ </msup>
+ </mrow>
+ </mrow>
+ </mtd>
+ </mlabeledtr>
+ </mtable>
+ <mrow>
+ <mo> ( </mo>
+ <mtable>
+ <mtr id="mtr">
+ <mtd id="mtd"> <mn>1</mn> </mtd>
+ <mtd> <mn>0</mn> </mtd>
+ <mtd> <mn>0</mn> </mtd>
+ </mtr>
+ <mtr>
+ <mtd> <mn>0</mn> </mtd>
+ <mtd> <mn>1</mn> </mtd>
+ <mtd> <mn>0</mn> </mtd>
+ </mtr>
+ <mtr>
+ <mtd> <mn>0</mn> </mtd>
+ <mtd> <mn>0</mn> </mtd>
+ <mtd> <mn>1</mn> </mtd>
+ </mtr>
+ </mtable>
+ <mo> ) </mo>
+ </mrow>
+
+ <maction id="maction" actiontype="toggle" selection="1">
+ <mfrac>
+ <mn>6</mn>
+ <mn>8</mn>
+ </mfrac>
+ <mfrac>
+ <mrow>
+ <mn>3</mn>
+ <mo>⋅</mo>
+ <mn>2</mn>
+ </mrow>
+ <mrow>
+ <mn>4</mn>
+ <mo>⋅</mo>
+ <mn>2</mn>
+ </mrow>
+ </mfrac>
+ <mfrac>
+ <mn>3</mn>
+ <mn>4</mn>
+ </mfrac>
+ </maction>
+
+ <merror id="merror">
+ <mrow>
+ <mtext>Division by zero: </mtext>
+ <mfrac>
+ <mn>1</mn>
+ <mn>0</mn>
+ </mfrac>
+ </mrow>
+ </merror>
+
+ <semantics id="semantics">
+ <!-- Presentation MathML -->
+ <mrow>
+ <msup>
+ <mi>x</mi>
+ <mn>2</mn>
+ </msup>
+ <mo>+</mo>
+ <mi>y</mi>
+ </mrow>
+ <!-- Content MathML -->
+ <annotation-xml id="annotation-xml" encoding="MathML-Content">
+ <apply>
+ <plus/>
+ <apply>
+ <power/>
+ <ci>x</ci>
+ <cn type="integer">2</cn>
+ </apply>
+ <ci>y</ci>
+ </apply>
+ </annotation-xml>
+ <!-- annotate TeX -->
+ <annotation id="annotation" encoding="application/x-tex">
+ x^{2} + y
+ </annotation>
+ </semantics>
+
+ <mstack id="mstack" align="center">
+ <mscarries id="mscarries" location="nw" position="1">
+ <none/>
+ <mscarry id="mscarry" crossout="updiagonalstrike">
+ <mn>1</mn>
+ </mscarry>
+ <mscarry location="w">
+ <mn>1</mn>
+ </mscarry>
+ </mscarries>
+ <mn>523</mn>
+ <msrow id="msrow" position="1">
+ <mo>-</mo>
+ <none/>
+ <mn>15</mn>
+ </msrow>
+ <msline id="msline" position="1"/>
+ <mn>508</mn>
+ </mstack>
+ <mspace width="1em"/>
+ <mlongdiv id="mlongdiv" longdivstyle="stackedrightright">
+ <mn>5</mn>
+ <mn>1</mn>
+ <mn>5</mn>
+ </mlongdiv>
+
+ <mstack>
+ <msgroup id="msgroup" position="2" shift="-1">
+ <mn>123</mn>
+ <msrow><mo>&#xD7;<!--MULTIPLICATION SIGN--></mo><mn>321</mn></msrow>
+ </msgroup>
+ <msline/>
+ <msgroup shift="1">
+ <mn>123</mn>
+ <mn>246</mn>
+ <mn>369</mn>
+ </msgroup>
+ <msline/>
+ </mstack>
+ </math>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/elm/test_canvas.html b/accessible/tests/mochitest/elm/test_canvas.html
new file mode 100644
index 000000000..b4b743800
--- /dev/null
+++ b/accessible/tests/mochitest/elm/test_canvas.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Accessible boundaries for hit regions</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../layout.js"></script>
+
+ <script type="application/javascript">
+ var kX = 10, kY = 10, kWidth = 150, kHeight = 100;
+ function doTest()
+ {
+ var canv = document.getElementById("c");
+ var context = canv.getContext('2d');
+ var element = document.getElementById("showA");
+ context.beginPath();
+ context.rect(kX, kY, kWidth, kHeight);
+ context.addHitRegion({control: element});
+
+ var input = getAccessible("showA");
+ var [cnvX, cnvY, cnvWidth, cnvHeight] = getBoundsForDOMElm(canv);
+ var [accX, accY, accWidth, accHeight] = getBounds(input);
+
+ var [x, y, w, h] = CSSToDevicePixels(window, kX, kY, kWidth, kHeight);
+ is(accX, cnvX + x, "wrong accX");
+ is(accY, cnvY + y, "wrong accY");
+ is(accWidth, w, "wrong accWidth");
+ is(accHeight, h, "wrong accHeight");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(function() {
+ SpecialPowers.pushPrefEnv({"set": [['canvas.hitregions.enabled', true]]}, doTest);
+ });
+
+ </script>
+</head>
+<body>
+
+ <canvas id="c">
+ <input id="showA" type="checkbox"><label for="showA"> Show As </label>
+ </canvas>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/elm/test_figure.html b/accessible/tests/mochitest/elm/test_figure.html
new file mode 100644
index 000000000..ba1bb489f
--- /dev/null
+++ b/accessible/tests/mochitest/elm/test_figure.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML5 figure/figcaption tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+ <script type="application/javascript"
+ src="../relations.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest()
+ {
+ testRole("figure", ROLE_FIGURE);
+ testRole("figcaption", ROLE_CAPTION);
+
+ todo(false, "figure name gets extra whitespace in the end!");
+ testName("figure", "figure caption ");
+ testName("figcaption", null);
+
+ testRelation("figure", RELATION_LABELLED_BY, "figcaption");
+ testRelation("figcaption", RELATION_LABEL_FOR, "figure");
+
+ testAttrs("figure", {"xml-roles" : "figure"}, true);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Implement figure and figcaption accessibility"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=658272">
+ Mozilla Bug 658272
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <figure id="figure">
+ <figcaption id="figcaption">figure caption</figcaption>
+ </figure>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/elm/test_listbox.xul b/accessible/tests/mochitest/elm/test_listbox.xul
new file mode 100644
index 000000000..e284b3e5c
--- /dev/null
+++ b/accessible/tests/mochitest/elm/test_listbox.xul
@@ -0,0 +1,74 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL listbox element test.">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ function doTest()
+ {
+ var id = "";
+ var listbox = null, acc = null;
+
+ //////////////////////////////////////////////////////////////////////////
+ // Simple listbox. There is no nsIAccessibleTable interface.
+
+ id = "listbox1";
+ acc = getAccessible(id);
+
+ // query nsIAccessibleTable
+ try {
+ acc.QueryInterface(nsIAccessibleTable);
+ ok(false,
+ id + " shouldn't implement nsIAccessibleTable interface.");
+ } catch(e) {
+ ok(true, id + " doesn't implement nsIAccessibleTable interface.");
+ }
+
+ // role
+ testRole(id, ROLE_LISTBOX);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=418371"
+ title="implement the rest of methods of nsIAccessibleTable on xul:listbox">
+ Mozilla Bug 418371
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <label control="listbox1" value="listbox: "/>
+ <listbox id="listbox1">
+ <listitem label="item1" id="item1"/>
+ <listitem label="item2" id="item2"/>
+ </listbox>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/elm/test_nsApplicationAcc.html b/accessible/tests/mochitest/elm/test_nsApplicationAcc.html
new file mode 100644
index 000000000..58763e437
--- /dev/null
+++ b/accessible/tests/mochitest/elm/test_nsApplicationAcc.html
@@ -0,0 +1,75 @@
+<html>
+
+<head>
+ <title>application accessible name</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ var accessible = getApplicationAccessible();
+ if (!accessible) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var bundleServ =
+ Components.classes["@mozilla.org/intl/stringbundle;1"].
+ getService(Components.interfaces.nsIStringBundleService);
+ var brandBundle =
+ bundleServ.createBundle("chrome://branding/locale/brand.properties");
+
+ var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].
+ getService(Components.interfaces.nsIXULAppInfo);
+
+ // nsIAccessible::name
+ var applicationName = "";
+ if (LINUX || SOLARIS) {
+ applicationName = appInfo.name;
+ } else {
+ try {
+ applicationName = brandBundle.GetStringFromName("brandShortName");
+ } catch(e) {
+ }
+
+ if (applicationName == "")
+ applicationName = "Gecko based application";
+ }
+ is (accessible.name, applicationName, "wrong application accessible name");
+
+ // nsIAccessibleApplication
+ is(accessible.appName, appInfo.name, "Wrong application name");
+ is(accessible.appVersion, appInfo.version, "Wrong application version");
+ is(accessible.platformName, "Gecko", "Wrong platform name");
+ is(accessible.platformVersion, appInfo.platformVersion,
+ "Wrong platform version");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+ </head>
+ <body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=456121"
+ title="nsApplicationAccessible::GetName does not return a default value when brand.properties does not exist">
+ Mozilla Bug 454211
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ </body>
+</html>
diff --git a/accessible/tests/mochitest/elm/test_plugin.html b/accessible/tests/mochitest/elm/test_plugin.html
new file mode 100644
index 000000000..3350e6ccc
--- /dev/null
+++ b/accessible/tests/mochitest/elm/test_plugin.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Plugin tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest()
+ {
+ if (!WIN) {
+ ok(true,
+ "It's Windows specific test. Feel free to extend the test.");
+
+ SimpleTest.finish();
+ return;
+ }
+
+ testStates("plugin-windowless", STATE_UNAVAILABLE);
+ testAccessibleTree("plugin-windowless", { EMBEDDED_OBJECT: [ ] });
+
+ testStates("plugin-windowless-fallback", STATE_UNAVAILABLE);
+ testAccessibleTree("plugin-windowless-fallback", { EMBEDDED_OBJECT: [ ] });
+
+ testStates("plugin-windowed", 0, 0, STATE_UNAVAILABLE);
+ testAccessibleTree("plugin-windowed", { EMBEDDED_OBJECT: [ { NOTHING: [] } ] });
+
+ testStates("plugin-windowed-fallback", 0, 0, STATE_UNAVAILABLE);
+ testAccessibleTree("plugin-windowed-fallback",
+ { EMBEDDED_OBJECT: [ { NOTHING: [] } ] });
+
+ // make sure we handle content changes under the plugin.
+ getNode("fallback1").setAttribute("href", "5");
+ getNode("fallback2").setAttribute("href", "5");
+ SimpleTest.executeSoon(function () { SimpleTest.finish(); });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Embed and object HTML tags should be given an accessible role of embedded object"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=485270">Bug 485270</a>
+ <a target="_blank"
+ title="Embedded object accessibles for inaccessible/windowless plugins should not expose a NULL child"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=816856">Bug 816856</a>
+ <a target="_blank"
+ title="Updating accessible tree for plugin with fallback shouldn't crash"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=881636">Bug 881636</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <embed id="plugin-windowless" type="application/x-test"
+ width="300" height="300"></embed>
+ <embed id="plugin-windowed" type="application/x-test" wmode="window"
+ width="300" height="300"></embed>
+ <embed id="plugin-windowless-fallback" type="application/x-test"
+ width="300" height="300"><a id="fallback1">foo</a></embed>
+ <embed id="plugin-windowed-fallback" type="application/x-test" wmode="window"
+ width="300" height="300"><a id="fallback2">foo</a></embed>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/elm/test_shadowroot.html b/accessible/tests/mochitest/elm/test_shadowroot.html
new file mode 100644
index 000000000..e4c39b8d6
--- /dev/null
+++ b/accessible/tests/mochitest/elm/test_shadowroot.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>ShadowRoot tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ testElm("component", {
+ role: ROLE_GROUPING,
+ children: [
+ {
+ role: ROLE_PUSHBUTTON,
+ },
+ {
+ role: ROLE_LINK,
+ },
+ ]
+ });
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Ensure accessible objects are created for shadow root"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1026125">
+ Mozilla Bug 1026125
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div role="group" id="component"></div>
+ <script>
+ var component = document.getElementById('component');
+ var shadow = component.createShadowRoot();
+
+ shadow.innerHTML = '<button>Hello</button>' +
+ '<a href="#"> World</a>';
+ </script>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events.js b/accessible/tests/mochitest/events.js
new file mode 100644
index 000000000..d1e5ec8a0
--- /dev/null
+++ b/accessible/tests/mochitest/events.js
@@ -0,0 +1,2329 @@
+////////////////////////////////////////////////////////////////////////////////
+// Constants
+
+const EVENT_ALERT = nsIAccessibleEvent.EVENT_ALERT;
+const EVENT_DESCRIPTION_CHANGE = nsIAccessibleEvent.EVENT_DESCRIPTION_CHANGE;
+const EVENT_DOCUMENT_LOAD_COMPLETE = nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE;
+const EVENT_DOCUMENT_RELOAD = nsIAccessibleEvent.EVENT_DOCUMENT_RELOAD;
+const EVENT_DOCUMENT_LOAD_STOPPED = nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_STOPPED;
+const EVENT_HIDE = nsIAccessibleEvent.EVENT_HIDE;
+const EVENT_FOCUS = nsIAccessibleEvent.EVENT_FOCUS;
+const EVENT_NAME_CHANGE = nsIAccessibleEvent.EVENT_NAME_CHANGE;
+const EVENT_MENU_START = nsIAccessibleEvent.EVENT_MENU_START;
+const EVENT_MENU_END = nsIAccessibleEvent.EVENT_MENU_END;
+const EVENT_MENUPOPUP_START = nsIAccessibleEvent.EVENT_MENUPOPUP_START;
+const EVENT_MENUPOPUP_END = nsIAccessibleEvent.EVENT_MENUPOPUP_END;
+const EVENT_OBJECT_ATTRIBUTE_CHANGED = nsIAccessibleEvent.EVENT_OBJECT_ATTRIBUTE_CHANGED;
+const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER;
+const EVENT_SCROLLING_START = nsIAccessibleEvent.EVENT_SCROLLING_START;
+const EVENT_SELECTION = nsIAccessibleEvent.EVENT_SELECTION;
+const EVENT_SELECTION_ADD = nsIAccessibleEvent.EVENT_SELECTION_ADD;
+const EVENT_SELECTION_REMOVE = nsIAccessibleEvent.EVENT_SELECTION_REMOVE;
+const EVENT_SELECTION_WITHIN = nsIAccessibleEvent.EVENT_SELECTION_WITHIN;
+const EVENT_SHOW = nsIAccessibleEvent.EVENT_SHOW;
+const EVENT_STATE_CHANGE = nsIAccessibleEvent.EVENT_STATE_CHANGE;
+const EVENT_TEXT_ATTRIBUTE_CHANGED = nsIAccessibleEvent.EVENT_TEXT_ATTRIBUTE_CHANGED;
+const EVENT_TEXT_CARET_MOVED = nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED;
+const EVENT_TEXT_INSERTED = nsIAccessibleEvent.EVENT_TEXT_INSERTED;
+const EVENT_TEXT_REMOVED = nsIAccessibleEvent.EVENT_TEXT_REMOVED;
+const EVENT_TEXT_SELECTION_CHANGED = nsIAccessibleEvent.EVENT_TEXT_SELECTION_CHANGED;
+const EVENT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_VALUE_CHANGE;
+const EVENT_TEXT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_TEXT_VALUE_CHANGE;
+const EVENT_VIRTUALCURSOR_CHANGED = nsIAccessibleEvent.EVENT_VIRTUALCURSOR_CHANGED;
+
+const kNotFromUserInput = 0;
+const kFromUserInput = 1;
+
+////////////////////////////////////////////////////////////////////////////////
+// General
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+/**
+ * Set up this variable to dump events into DOM.
+ */
+var gA11yEventDumpID = "";
+
+/**
+ * Set up this variable to dump event processing into console.
+ */
+var gA11yEventDumpToConsole = false;
+
+/**
+ * Set up this variable to dump event processing into error console.
+ */
+var gA11yEventDumpToAppConsole = false;
+
+/**
+ * Semicolon separated set of logging features.
+ */
+var gA11yEventDumpFeature = "";
+
+/**
+ * Executes the function when requested event is handled.
+ *
+ * @param aEventType [in] event type
+ * @param aTarget [in] event target
+ * @param aFunc [in] function to call when event is handled
+ * @param aContext [in, optional] object in which context the function is
+ * called
+ * @param aArg1 [in, optional] argument passed into the function
+ * @param aArg2 [in, optional] argument passed into the function
+ */
+function waitForEvent(aEventType, aTargetOrFunc, aFunc, aContext, aArg1, aArg2)
+{
+ var handler = {
+ handleEvent: function handleEvent(aEvent) {
+
+ var target = aTargetOrFunc;
+ if (typeof aTargetOrFunc == "function")
+ target = aTargetOrFunc.call();
+
+ if (target) {
+ if (target instanceof nsIAccessible &&
+ target != aEvent.accessible)
+ return;
+
+ if (target instanceof nsIDOMNode &&
+ target != aEvent.DOMNode)
+ return;
+ }
+
+ unregisterA11yEventListener(aEventType, this);
+
+ window.setTimeout(
+ function ()
+ {
+ aFunc.call(aContext, aArg1, aArg2);
+ },
+ 0
+ );
+ }
+ };
+
+ registerA11yEventListener(aEventType, handler);
+}
+
+/**
+ * Generate mouse move over image map what creates image map accessible (async).
+ * See waitForImageMap() function.
+ */
+function waveOverImageMap(aImageMapID)
+{
+ var imageMapNode = getNode(aImageMapID);
+ synthesizeMouse(imageMapNode, 10, 10, { type: "mousemove" },
+ imageMapNode.ownerDocument.defaultView);
+}
+
+/**
+ * Call the given function when the tree of the given image map is built.
+ */
+function waitForImageMap(aImageMapID, aTestFunc)
+{
+ waveOverImageMap(aImageMapID);
+
+ var imageMapAcc = getAccessible(aImageMapID);
+ if (imageMapAcc.firstChild)
+ return aTestFunc();
+
+ waitForEvent(EVENT_REORDER, imageMapAcc, aTestFunc);
+}
+
+/**
+ * Register accessibility event listener.
+ *
+ * @param aEventType the accessible event type (see nsIAccessibleEvent for
+ * available constants).
+ * @param aEventHandler event listener object, when accessible event of the
+ * given type is handled then 'handleEvent' method of
+ * this object is invoked with nsIAccessibleEvent object
+ * as the first argument.
+ */
+function registerA11yEventListener(aEventType, aEventHandler)
+{
+ listenA11yEvents(true);
+ addA11yEventListener(aEventType, aEventHandler);
+}
+
+/**
+ * Unregister accessibility event listener. Must be called for every registered
+ * event listener (see registerA11yEventListener() function) when the listener
+ * is not needed.
+ */
+function unregisterA11yEventListener(aEventType, aEventHandler)
+{
+ removeA11yEventListener(aEventType, aEventHandler);
+ listenA11yEvents(false);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Event queue
+
+/**
+ * Return value of invoke method of invoker object. Indicates invoker was unable
+ * to prepare action.
+ */
+const INVOKER_ACTION_FAILED = 1;
+
+/**
+ * Return value of eventQueue.onFinish. Indicates eventQueue should not finish
+ * tests.
+ */
+const DO_NOT_FINISH_TEST = 1;
+
+/**
+ * Creates event queue for the given event type. The queue consists of invoker
+ * objects, each of them generates the event of the event type. When queue is
+ * started then every invoker object is asked to generate event after timeout.
+ * When event is caught then current invoker object is asked to check whether
+ * event was handled correctly.
+ *
+ * Invoker interface is:
+ *
+ * var invoker = {
+ * // Generates accessible event or event sequence. If returns
+ * // INVOKER_ACTION_FAILED constant then stop tests.
+ * invoke: function(){},
+ *
+ * // [optional] Invoker's check of handled event for correctness.
+ * check: function(aEvent){},
+ *
+ * // [optional] Invoker's check before the next invoker is proceeded.
+ * finalCheck: function(aEvent){},
+ *
+ * // [optional] Is called when event of any registered type is handled.
+ * debugCheck: function(aEvent){},
+ *
+ * // [ignored if 'eventSeq' is defined] DOM node event is generated for
+ * // (used in the case when invoker expects single event).
+ * DOMNode getter: function() {},
+ *
+ * // [optional] if true then event sequences are ignored (no failure if
+ * // sequences are empty). Use you need to invoke an action, do some check
+ * // after timeout and proceed a next invoker.
+ * noEventsOnAction getter: function() {},
+ *
+ * // Array of checker objects defining expected events on invoker's action.
+ * //
+ * // Checker object interface:
+ * //
+ * // var checker = {
+ * // * DOM or a11y event type. *
+ * // type getter: function() {},
+ * //
+ * // * DOM node or accessible. *
+ * // target getter: function() {},
+ * //
+ * // * DOM event phase (false - bubbling). *
+ * // phase getter: function() {},
+ * //
+ * // * Callback, called to match handled event. *
+ * // match : function(aEvent) {},
+ * //
+ * // * Callback, called when event is handled
+ * // check: function(aEvent) {},
+ * //
+ * // * Checker ID *
+ * // getID: function() {},
+ * //
+ * // * Event that don't have predefined order relative other events. *
+ * // async getter: function() {},
+ * //
+ * // * Event that is not expected. *
+ * // unexpected getter: function() {},
+ * //
+ * // * No other event of the same type is not allowed. *
+ * // unique getter: function() {}
+ * // };
+ * eventSeq getter() {},
+ *
+ * // Array of checker objects defining unexpected events on invoker's
+ * // action.
+ * unexpectedEventSeq getter() {},
+ *
+ * // The ID of invoker.
+ * getID: function(){} // returns invoker ID
+ * };
+ *
+ * // Used to add a possible scenario of expected/unexpected events on
+ * // invoker's action.
+ * defineScenario(aInvokerObj, aEventSeq, aUnexpectedEventSeq)
+ *
+ *
+ * @param aEventType [in, optional] the default event type (isn't used if
+ * invoker defines eventSeq property).
+ */
+function eventQueue(aEventType)
+{
+ // public
+
+ /**
+ * Add invoker object into queue.
+ */
+ this.push = function eventQueue_push(aEventInvoker)
+ {
+ this.mInvokers.push(aEventInvoker);
+ }
+
+ /**
+ * Start the queue processing.
+ */
+ this.invoke = function eventQueue_invoke()
+ {
+ listenA11yEvents(true);
+
+ // XXX: Intermittent test_events_caretmove.html fails withouth timeout,
+ // see bug 474952.
+ this.processNextInvokerInTimeout(true);
+ }
+
+ /**
+ * This function is called when all events in the queue were handled.
+ * Override it if you need to be notified of this.
+ */
+ this.onFinish = function eventQueue_finish()
+ {
+ }
+
+ // private
+
+ /**
+ * Process next invoker.
+ */
+ this.processNextInvoker = function eventQueue_processNextInvoker()
+ {
+ // Some scenario was matched, we wait on next invoker processing.
+ if (this.mNextInvokerStatus == kInvokerCanceled) {
+ this.setInvokerStatus(kInvokerNotScheduled,
+ "scenario was matched, wait for next invoker activation");
+ return;
+ }
+
+ this.setInvokerStatus(kInvokerNotScheduled, "the next invoker is processed now");
+
+ // Finish processing of the current invoker if any.
+ var testFailed = false;
+
+ var invoker = this.getInvoker();
+ if (invoker) {
+ if ("finalCheck" in invoker)
+ invoker.finalCheck();
+
+ if (this.mScenarios && this.mScenarios.length) {
+ var matchIdx = -1;
+ for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
+ var eventSeq = this.mScenarios[scnIdx];
+ if (!this.areExpectedEventsLeft(eventSeq)) {
+ for (var idx = 0; idx < eventSeq.length; idx++) {
+ var checker = eventSeq[idx];
+ if (checker.unexpected && checker.wasCaught ||
+ !checker.unexpected && checker.wasCaught != 1) {
+ break;
+ }
+ }
+
+ // Ok, we have matched scenario. Report it was completed ok. In
+ // case of empty scenario guess it was matched but if later we
+ // find out that non empty scenario was matched then it will be
+ // a final match.
+ if (idx == eventSeq.length) {
+ if (matchIdx != -1 && eventSeq.length > 0 &&
+ this.mScenarios[matchIdx].length > 0) {
+ ok(false,
+ "We have a matched scenario at index " + matchIdx + " already.");
+ }
+
+ if (matchIdx == -1 || eventSeq.length > 0)
+ matchIdx = scnIdx;
+
+ // Report everything is ok.
+ for (var idx = 0; idx < eventSeq.length; idx++) {
+ var checker = eventSeq[idx];
+
+ var typeStr = eventQueue.getEventTypeAsString(checker);
+ var msg = "Test with ID = '" + this.getEventID(checker) +
+ "' succeed. ";
+
+ if (checker.unexpected) {
+ ok(true, msg + `There's no unexpected '${typeStr}' event.`);
+ }
+ else {
+ if (checker.todo) {
+ todo(false, `Todo event '${typeStr}' was caught`);
+ }
+ else {
+ ok(true, `${msg} Event '${typeStr}' was handled.`);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // We don't have completely matched scenario. Report each failure/success
+ // for every scenario.
+ if (matchIdx == -1) {
+ testFailed = true;
+ for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
+ var eventSeq = this.mScenarios[scnIdx];
+ for (var idx = 0; idx < eventSeq.length; idx++) {
+ var checker = eventSeq[idx];
+
+ var typeStr = eventQueue.getEventTypeAsString(checker);
+ var msg = "Scenario #" + scnIdx + " of test with ID = '" +
+ this.getEventID(checker) + "' failed. ";
+
+ if (checker.wasCaught > 1)
+ ok(false, msg + "Dupe " + typeStr + " event.");
+
+ if (checker.unexpected) {
+ if (checker.wasCaught) {
+ ok(false, msg + "There's unexpected " + typeStr + " event.");
+ }
+ }
+ else if (!checker.wasCaught) {
+ var rf = checker.todo ? todo : ok;
+ rf(false, `${msg} '${typeStr} event is missed.`);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ this.clearEventHandler();
+
+ // Check if need to stop the test.
+ if (testFailed || this.mIndex == this.mInvokers.length - 1) {
+ listenA11yEvents(false);
+
+ var res = this.onFinish();
+ if (res != DO_NOT_FINISH_TEST)
+ SimpleTest.executeSoon(SimpleTest.finish);
+
+ return;
+ }
+
+ // Start processing of next invoker.
+ invoker = this.getNextInvoker();
+
+ // Set up event listeners. Process a next invoker if no events were added.
+ if (!this.setEventHandler(invoker)) {
+ this.processNextInvoker();
+ return;
+ }
+
+ if (gLogger.isEnabled()) {
+ gLogger.logToConsole("Event queue: \n invoke: " + invoker.getID());
+ gLogger.logToDOM("EQ: invoke: " + invoker.getID(), true);
+ }
+
+ var infoText = "Invoke the '" + invoker.getID() + "' test { ";
+ var scnCount = this.mScenarios ? this.mScenarios.length : 0;
+ for (var scnIdx = 0; scnIdx < scnCount; scnIdx++) {
+ infoText += "scenario #" + scnIdx + ": ";
+ var eventSeq = this.mScenarios[scnIdx];
+ for (var idx = 0; idx < eventSeq.length; idx++) {
+ infoText += eventSeq[idx].unexpected ? "un" : "" +
+ "expected '" + eventQueue.getEventTypeAsString(eventSeq[idx]) +
+ "' event; ";
+ }
+ }
+ infoText += " }";
+ info(infoText);
+
+ if (invoker.invoke() == INVOKER_ACTION_FAILED) {
+ // Invoker failed to prepare action, fail and finish tests.
+ this.processNextInvoker();
+ return;
+ }
+
+ if (this.hasUnexpectedEventsScenario())
+ this.processNextInvokerInTimeout(true);
+ }
+
+ this.processNextInvokerInTimeout =
+ function eventQueue_processNextInvokerInTimeout(aUncondProcess)
+ {
+ this.setInvokerStatus(kInvokerPending, "Process next invoker in timeout");
+
+ // No need to wait extra timeout when a) we know we don't need to do that
+ // and b) there's no any single unexpected event.
+ if (!aUncondProcess && this.areAllEventsExpected()) {
+ // We need delay to avoid events coalesce from different invokers.
+ var queue = this;
+ SimpleTest.executeSoon(function() { queue.processNextInvoker(); });
+ return;
+ }
+
+ // Check in timeout invoker didn't fire registered events.
+ window.setTimeout(function(aQueue) { aQueue.processNextInvoker(); }, 300,
+ this);
+ }
+
+ /**
+ * Handle events for the current invoker.
+ */
+ this.handleEvent = function eventQueue_handleEvent(aEvent)
+ {
+ var invoker = this.getInvoker();
+ if (!invoker) // skip events before test was started
+ return;
+
+ if (!this.mScenarios) {
+ // Bad invoker object, error will be reported before processing of next
+ // invoker in the queue.
+ this.processNextInvoker();
+ return;
+ }
+
+ if ("debugCheck" in invoker)
+ invoker.debugCheck(aEvent);
+
+ for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
+ var eventSeq = this.mScenarios[scnIdx];
+ for (var idx = 0; idx < eventSeq.length; idx++) {
+ var checker = eventSeq[idx];
+
+ // Search through handled expected events to report error if one of them
+ // is handled for a second time.
+ if (!checker.unexpected && (checker.wasCaught > 0) &&
+ eventQueue.isSameEvent(checker, aEvent)) {
+ checker.wasCaught++;
+ continue;
+ }
+
+ // Search through unexpected events, any match results in error report
+ // after this invoker processing (in case of matched scenario only).
+ if (checker.unexpected && eventQueue.compareEvents(checker, aEvent)) {
+ checker.wasCaught++;
+ continue;
+ }
+
+ // Report an error if we hanlded not expected event of unique type
+ // (i.e. event types are matched, targets differs).
+ if (!checker.unexpected && checker.unique &&
+ eventQueue.compareEventTypes(checker, aEvent)) {
+ var isExppected = false;
+ for (var jdx = 0; jdx < eventSeq.length; jdx++) {
+ isExpected = eventQueue.compareEvents(eventSeq[jdx], aEvent);
+ if (isExpected)
+ break;
+ }
+
+ if (!isExpected) {
+ ok(false,
+ "Unique type " +
+ eventQueue.getEventTypeAsString(checker) + " event was handled.");
+ }
+ }
+ }
+ }
+
+ var hasMatchedCheckers = false;
+ for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
+ var eventSeq = this.mScenarios[scnIdx];
+
+ // Check if handled event matches expected sync event.
+ var nextChecker = this.getNextExpectedEvent(eventSeq);
+ if (nextChecker) {
+ if (eventQueue.compareEvents(nextChecker, aEvent)) {
+ this.processMatchedChecker(aEvent, nextChecker, scnIdx, eventSeq.idx);
+ hasMatchedCheckers = true;
+ continue;
+ }
+ }
+
+ // Check if handled event matches any expected async events.
+ var haveUnmatchedAsync = false;
+ for (idx = 0; idx < eventSeq.length; idx++) {
+ if (eventSeq[idx] instanceof orderChecker && haveUnmatchedAsync) {
+ break;
+ }
+
+ if (!eventSeq[idx].wasCaught) {
+ haveUnmatchedAsync = true;
+ }
+
+ if (!eventSeq[idx].unexpected && eventSeq[idx].async) {
+ if (eventQueue.compareEvents(eventSeq[idx], aEvent)) {
+ this.processMatchedChecker(aEvent, eventSeq[idx], scnIdx, idx);
+ hasMatchedCheckers = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (hasMatchedCheckers) {
+ var invoker = this.getInvoker();
+ if ("check" in invoker)
+ invoker.check(aEvent);
+ }
+
+ for (idx = 0; idx < eventSeq.length; idx++) {
+ if (!eventSeq[idx].wasCaught) {
+ if (eventSeq[idx] instanceof orderChecker) {
+ eventSeq[idx].wasCaught++;
+ } else {
+ break;
+ }
+ }
+ }
+
+ // If we don't have more events to wait then schedule next invoker.
+ if (this.hasMatchedScenario()) {
+ if (this.mNextInvokerStatus == kInvokerNotScheduled) {
+ this.processNextInvokerInTimeout();
+
+ } else if (this.mNextInvokerStatus == kInvokerCanceled) {
+ this.setInvokerStatus(kInvokerPending,
+ "Full match. Void the cancelation of next invoker processing");
+ }
+ return;
+ }
+
+ // If we have scheduled a next invoker then cancel in case of match.
+ if ((this.mNextInvokerStatus == kInvokerPending) && hasMatchedCheckers) {
+ this.setInvokerStatus(kInvokerCanceled,
+ "Cancel the scheduled invoker in case of match");
+ }
+ }
+
+ // Helpers
+ this.processMatchedChecker =
+ function eventQueue_function(aEvent, aMatchedChecker, aScenarioIdx, aEventIdx)
+ {
+ aMatchedChecker.wasCaught++;
+
+ if ("check" in aMatchedChecker)
+ aMatchedChecker.check(aEvent);
+
+ eventQueue.logEvent(aEvent, aMatchedChecker, aScenarioIdx, aEventIdx,
+ this.areExpectedEventsLeft(),
+ this.mNextInvokerStatus);
+ }
+
+ this.getNextExpectedEvent =
+ function eventQueue_getNextExpectedEvent(aEventSeq)
+ {
+ if (!("idx" in aEventSeq))
+ aEventSeq.idx = 0;
+
+ while (aEventSeq.idx < aEventSeq.length &&
+ (aEventSeq[aEventSeq.idx].unexpected ||
+ aEventSeq[aEventSeq.idx].todo ||
+ aEventSeq[aEventSeq.idx].async ||
+ aEventSeq[aEventSeq.idx] instanceof orderChecker ||
+ aEventSeq[aEventSeq.idx].wasCaught > 0)) {
+ aEventSeq.idx++;
+ }
+
+ return aEventSeq.idx != aEventSeq.length ? aEventSeq[aEventSeq.idx] : null;
+ }
+
+ this.areExpectedEventsLeft =
+ function eventQueue_areExpectedEventsLeft(aScenario)
+ {
+ function scenarioHasUnhandledExpectedEvent(aEventSeq)
+ {
+ // Check if we have unhandled async (can be anywhere in the sequance) or
+ // sync expcected events yet.
+ for (var idx = 0; idx < aEventSeq.length; idx++) {
+ if (!aEventSeq[idx].unexpected && !aEventSeq[idx].todo &&
+ !aEventSeq[idx].wasCaught && !(aEventSeq[idx] instanceof orderChecker))
+ return true;
+ }
+
+ return false;
+ }
+
+ if (aScenario)
+ return scenarioHasUnhandledExpectedEvent(aScenario);
+
+ for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
+ var eventSeq = this.mScenarios[scnIdx];
+ if (scenarioHasUnhandledExpectedEvent(eventSeq))
+ return true;
+ }
+ return false;
+ }
+
+ this.areAllEventsExpected =
+ function eventQueue_areAllEventsExpected()
+ {
+ for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
+ var eventSeq = this.mScenarios[scnIdx];
+ for (var idx = 0; idx < eventSeq.length; idx++) {
+ if (eventSeq[idx].unexpected || eventSeq[idx].todo)
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ this.isUnexpectedEventScenario =
+ function eventQueue_isUnexpectedEventsScenario(aScenario)
+ {
+ for (var idx = 0; idx < aScenario.length; idx++) {
+ if (!aScenario[idx].unexpected && !aScenario[idx].todo)
+ break;
+ }
+
+ return idx == aScenario.length;
+ }
+
+ this.hasUnexpectedEventsScenario =
+ function eventQueue_hasUnexpectedEventsScenario()
+ {
+ if (this.getInvoker().noEventsOnAction)
+ return true;
+
+ for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
+ if (this.isUnexpectedEventScenario(this.mScenarios[scnIdx]))
+ return true;
+ }
+
+ return false;
+ }
+
+ this.hasMatchedScenario =
+ function eventQueue_hasMatchedScenario()
+ {
+ for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
+ var scn = this.mScenarios[scnIdx];
+ if (!this.isUnexpectedEventScenario(scn) && !this.areExpectedEventsLeft(scn))
+ return true;
+ }
+ return false;
+ }
+
+ this.getInvoker = function eventQueue_getInvoker()
+ {
+ return this.mInvokers[this.mIndex];
+ }
+
+ this.getNextInvoker = function eventQueue_getNextInvoker()
+ {
+ return this.mInvokers[++this.mIndex];
+ }
+
+ this.setEventHandler = function eventQueue_setEventHandler(aInvoker)
+ {
+ if (!("scenarios" in aInvoker) || aInvoker.scenarios.length == 0) {
+ var eventSeq = aInvoker.eventSeq;
+ var unexpectedEventSeq = aInvoker.unexpectedEventSeq;
+ if (!eventSeq && !unexpectedEventSeq && this.mDefEventType)
+ eventSeq = [ new invokerChecker(this.mDefEventType, aInvoker.DOMNode) ];
+
+ if (eventSeq || unexpectedEventSeq)
+ defineScenario(aInvoker, eventSeq, unexpectedEventSeq);
+ }
+
+ if (aInvoker.noEventsOnAction)
+ return true;
+
+ this.mScenarios = aInvoker.scenarios;
+ if (!this.mScenarios || !this.mScenarios.length) {
+ ok(false, "Broken invoker '" + aInvoker.getID() + "'");
+ return false;
+ }
+
+ // Register event listeners.
+ for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
+ var eventSeq = this.mScenarios[scnIdx];
+
+ if (gLogger.isEnabled()) {
+ var msg = "scenario #" + scnIdx +
+ ", registered events number: " + eventSeq.length;
+ gLogger.logToConsole(msg);
+ gLogger.logToDOM(msg, true);
+ }
+
+ // Do not warn about empty event sequances when more than one scenario
+ // was registered.
+ if (this.mScenarios.length == 1 && eventSeq.length == 0) {
+ ok(false,
+ "Broken scenario #" + scnIdx + " of invoker '" + aInvoker.getID() +
+ "'. No registered events");
+ return false;
+ }
+
+ for (var idx = 0; idx < eventSeq.length; idx++)
+ eventSeq[idx].wasCaught = 0;
+
+ for (var idx = 0; idx < eventSeq.length; idx++) {
+ if (gLogger.isEnabled()) {
+ var msg = "registered";
+ if (eventSeq[idx].unexpected)
+ msg += " unexpected";
+ if (eventSeq[idx].async)
+ msg += " async";
+
+ msg += ": event type: " +
+ eventQueue.getEventTypeAsString(eventSeq[idx]) +
+ ", target: " + eventQueue.getEventTargetDescr(eventSeq[idx], true);
+
+ gLogger.logToConsole(msg);
+ gLogger.logToDOM(msg, true);
+ }
+
+ var eventType = eventSeq[idx].type;
+ if (typeof eventType == "string") {
+ // DOM event
+ var target = eventSeq[idx].target;
+ if (!target) {
+ ok(false, "no target for DOM event!");
+ return false;
+ }
+ var phase = eventQueue.getEventPhase(eventSeq[idx]);
+ target.ownerDocument.addEventListener(eventType, this, phase);
+
+ } else {
+ // A11y event
+ addA11yEventListener(eventType, this);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ this.clearEventHandler = function eventQueue_clearEventHandler()
+ {
+ if (!this.mScenarios)
+ return;
+
+ for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
+ var eventSeq = this.mScenarios[scnIdx];
+ for (var idx = 0; idx < eventSeq.length; idx++) {
+ var eventType = eventSeq[idx].type;
+ if (typeof eventType == "string") {
+ // DOM event
+ var target = eventSeq[idx].target;
+ var phase = eventQueue.getEventPhase(eventSeq[idx]);
+ target.ownerDocument.removeEventListener(eventType, this, phase);
+
+ } else {
+ // A11y event
+ removeA11yEventListener(eventType, this);
+ }
+ }
+ }
+ this.mScenarios = null;
+ }
+
+ this.getEventID = function eventQueue_getEventID(aChecker)
+ {
+ if ("getID" in aChecker)
+ return aChecker.getID();
+
+ var invoker = this.getInvoker();
+ return invoker.getID();
+ }
+
+ this.setInvokerStatus = function eventQueue_setInvokerStatus(aStatus, aLogMsg)
+ {
+ this.mNextInvokerStatus = aStatus;
+
+ // Uncomment it to debug invoker processing logic.
+ //gLogger.log(eventQueue.invokerStatusToMsg(aStatus, aLogMsg));
+ }
+
+ this.mDefEventType = aEventType;
+
+ this.mInvokers = new Array();
+ this.mIndex = -1;
+ this.mScenarios = null;
+
+ this.mNextInvokerStatus = kInvokerNotScheduled;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// eventQueue static members and constants
+
+const kInvokerNotScheduled = 0;
+const kInvokerPending = 1;
+const kInvokerCanceled = 2;
+
+eventQueue.getEventTypeAsString =
+ function eventQueue_getEventTypeAsString(aEventOrChecker)
+{
+ if (aEventOrChecker instanceof nsIDOMEvent)
+ return aEventOrChecker.type;
+
+ if (aEventOrChecker instanceof nsIAccessibleEvent)
+ return eventTypeToString(aEventOrChecker.eventType);
+
+ return (typeof aEventOrChecker.type == "string") ?
+ aEventOrChecker.type : eventTypeToString(aEventOrChecker.type);
+}
+
+eventQueue.getEventTargetDescr =
+ function eventQueue_getEventTargetDescr(aEventOrChecker, aDontForceTarget)
+{
+ if (aEventOrChecker instanceof nsIDOMEvent)
+ return prettyName(aEventOrChecker.originalTarget);
+
+ if (aEventOrChecker instanceof nsIDOMEvent)
+ return prettyName(aEventOrChecker.accessible);
+
+ var descr = aEventOrChecker.targetDescr;
+ if (descr)
+ return descr;
+
+ if (aDontForceTarget)
+ return "no target description";
+
+ var target = ("target" in aEventOrChecker) ? aEventOrChecker.target : null;
+ return prettyName(target);
+}
+
+eventQueue.getEventPhase = function eventQueue_getEventPhase(aChecker)
+{
+ return ("phase" in aChecker) ? aChecker.phase : true;
+}
+
+eventQueue.compareEventTypes =
+ function eventQueue_compareEventTypes(aChecker, aEvent)
+{
+ var eventType = (aEvent instanceof nsIDOMEvent) ?
+ aEvent.type : aEvent.eventType;
+ return aChecker.type == eventType;
+}
+
+eventQueue.compareEvents = function eventQueue_compareEvents(aChecker, aEvent)
+{
+ if (!eventQueue.compareEventTypes(aChecker, aEvent))
+ return false;
+
+ // If checker provides "match" function then allow the checker to decide
+ // whether event is matched.
+ if ("match" in aChecker)
+ return aChecker.match(aEvent);
+
+ var target1 = aChecker.target;
+ if (target1 instanceof nsIAccessible) {
+ var target2 = (aEvent instanceof nsIDOMEvent) ?
+ getAccessible(aEvent.target) : aEvent.accessible;
+
+ return target1 == target2;
+ }
+
+ // If original target isn't suitable then extend interface to support target
+ // (original target is used in test_elm_media.html).
+ var target2 = (aEvent instanceof nsIDOMEvent) ?
+ aEvent.originalTarget : aEvent.DOMNode;
+ return target1 == target2;
+}
+
+eventQueue.isSameEvent = function eventQueue_isSameEvent(aChecker, aEvent)
+{
+ // We don't have stored info about handled event other than its type and
+ // target, thus we should filter text change and state change events since
+ // they may occur on the same element because of complex changes.
+ return this.compareEvents(aChecker, aEvent) &&
+ !(aEvent instanceof nsIAccessibleTextChangeEvent) &&
+ !(aEvent instanceof nsIAccessibleStateChangeEvent);
+}
+
+eventQueue.invokerStatusToMsg =
+ function eventQueue_invokerStatusToMsg(aInvokerStatus, aMsg)
+{
+ var msg = "invoker status: ";
+ switch (aInvokerStatus) {
+ case kInvokerNotScheduled:
+ msg += "not scheduled";
+ break;
+ case kInvokerPending:
+ msg += "pending";
+ break;
+ case kInvokerCanceled:
+ msg += "canceled";
+ break;
+ }
+
+ if (aMsg)
+ msg += " (" + aMsg + ")";
+
+ return msg;
+}
+
+eventQueue.logEvent = function eventQueue_logEvent(aOrigEvent, aMatchedChecker,
+ aScenarioIdx, aEventIdx,
+ aAreExpectedEventsLeft,
+ aInvokerStatus)
+{
+ // Dump DOM event information. Skip a11y event since it is dumped by
+ // gA11yEventObserver.
+ if (aOrigEvent instanceof nsIDOMEvent) {
+ var info = "Event type: " + eventQueue.getEventTypeAsString(aOrigEvent);
+ info += ". Target: " + eventQueue.getEventTargetDescr(aOrigEvent);
+ gLogger.logToDOM(info);
+ }
+
+ var infoMsg = "unhandled expected events: " + aAreExpectedEventsLeft +
+ ", " + eventQueue.invokerStatusToMsg(aInvokerStatus);
+
+ var currType = eventQueue.getEventTypeAsString(aMatchedChecker);
+ var currTargetDescr = eventQueue.getEventTargetDescr(aMatchedChecker);
+ var consoleMsg = "*****\nScenario " + aScenarioIdx +
+ ", event " + aEventIdx + " matched: " + currType + "\n" + infoMsg + "\n*****";
+ gLogger.logToConsole(consoleMsg);
+
+ var emphText = "matched ";
+ var msg = "EQ event, type: " + currType + ", target: " + currTargetDescr +
+ ", " + infoMsg;
+ gLogger.logToDOM(msg, true, emphText);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Action sequence
+
+/**
+ * Deal with action sequence. Used when you need to execute couple of actions
+ * each after other one.
+ */
+function sequence()
+{
+ /**
+ * Append new sequence item.
+ *
+ * @param aProcessor [in] object implementing interface
+ * {
+ * // execute item action
+ * process: function() {},
+ * // callback, is called when item was processed
+ * onProcessed: function() {}
+ * };
+ * @param aEventType [in] event type of expected event on item action
+ * @param aTarget [in] event target of expected event on item action
+ * @param aItemID [in] identifier of item
+ */
+ this.append = function sequence_append(aProcessor, aEventType, aTarget,
+ aItemID)
+ {
+ var item = new sequenceItem(aProcessor, aEventType, aTarget, aItemID);
+ this.items.push(item);
+ }
+
+ /**
+ * Process next sequence item.
+ */
+ this.processNext = function sequence_processNext()
+ {
+ this.idx++;
+ if (this.idx >= this.items.length) {
+ ok(false, "End of sequence: nothing to process!");
+ SimpleTest.finish();
+ return;
+ }
+
+ this.items[this.idx].startProcess();
+ }
+
+ this.items = new Array();
+ this.idx = -1;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Event queue invokers
+
+/**
+ * Defines a scenario of expected/unexpected events. Each invoker can have
+ * one or more scenarios of events. Only one scenario must be completed.
+ */
+function defineScenario(aInvoker, aEventSeq, aUnexpectedEventSeq)
+{
+ if (!("scenarios" in aInvoker))
+ aInvoker.scenarios = new Array();
+
+ // Create unified event sequence concatenating expected and unexpected
+ // events.
+ if (!aEventSeq)
+ aEventSeq = [];
+
+ for (var idx = 0; idx < aEventSeq.length; idx++) {
+ aEventSeq[idx].unexpected |= false;
+ aEventSeq[idx].async |= false;
+ }
+
+ if (aUnexpectedEventSeq) {
+ for (var idx = 0; idx < aUnexpectedEventSeq.length; idx++) {
+ aUnexpectedEventSeq[idx].unexpected = true;
+ aUnexpectedEventSeq[idx].async = false;
+ }
+
+ aEventSeq = aEventSeq.concat(aUnexpectedEventSeq);
+ }
+
+ aInvoker.scenarios.push(aEventSeq);
+}
+
+
+/**
+ * Invokers defined below take a checker object (or array of checker objects).
+ * An invoker listens for default event type registered in event queue object
+ * until its checker is provided.
+ *
+ * Note, checker object or array of checker objects is optional.
+ */
+
+/**
+ * Click invoker.
+ */
+function synthClick(aNodeOrID, aCheckerOrEventSeq, aArgs)
+{
+ this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);
+
+ this.invoke = function synthClick_invoke()
+ {
+ var targetNode = this.DOMNode;
+ if (targetNode instanceof nsIDOMDocument) {
+ targetNode =
+ this.DOMNode.body ? this.DOMNode.body : this.DOMNode.documentElement;
+ }
+
+ // Scroll the node into view, otherwise synth click may fail.
+ if (targetNode instanceof nsIDOMHTMLElement) {
+ targetNode.scrollIntoView(true);
+ } else if (targetNode instanceof nsIDOMXULElement) {
+ var targetAcc = getAccessible(targetNode);
+ targetAcc.scrollTo(SCROLL_TYPE_ANYWHERE);
+ }
+
+ var x = 1, y = 1;
+ if (aArgs && ("where" in aArgs) && aArgs.where == "right") {
+ if (targetNode instanceof nsIDOMHTMLElement)
+ x = targetNode.offsetWidth - 1;
+ else if (targetNode instanceof nsIDOMXULElement)
+ x = targetNode.boxObject.width - 1;
+ }
+ synthesizeMouse(targetNode, x, y, aArgs ? aArgs : {});
+ }
+
+ this.finalCheck = function synthClick_finalCheck()
+ {
+ // Scroll top window back.
+ window.top.scrollTo(0, 0);
+ }
+
+ this.getID = function synthClick_getID()
+ {
+ return prettyName(aNodeOrID) + " click";
+ }
+}
+
+/**
+ * Mouse move invoker.
+ */
+function synthMouseMove(aID, aCheckerOrEventSeq)
+{
+ this.__proto__ = new synthAction(aID, aCheckerOrEventSeq);
+
+ this.invoke = function synthMouseMove_invoke()
+ {
+ synthesizeMouse(this.DOMNode, 1, 1, { type: "mousemove" });
+ synthesizeMouse(this.DOMNode, 2, 2, { type: "mousemove" });
+ }
+
+ this.getID = function synthMouseMove_getID()
+ {
+ return prettyName(aID) + " mouse move";
+ }
+}
+
+/**
+ * General key press invoker.
+ */
+function synthKey(aNodeOrID, aKey, aArgs, aCheckerOrEventSeq)
+{
+ this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);
+
+ this.invoke = function synthKey_invoke()
+ {
+ synthesizeKey(this.mKey, this.mArgs, this.mWindow);
+ }
+
+ this.getID = function synthKey_getID()
+ {
+ var key = this.mKey;
+ switch (this.mKey) {
+ case "VK_TAB":
+ key = "tab";
+ break;
+ case "VK_DOWN":
+ key = "down";
+ break;
+ case "VK_UP":
+ key = "up";
+ break;
+ case "VK_LEFT":
+ key = "left";
+ break;
+ case "VK_RIGHT":
+ key = "right";
+ break;
+ case "VK_HOME":
+ key = "home";
+ break;
+ case "VK_END":
+ key = "end";
+ break;
+ case "VK_ESCAPE":
+ key = "escape";
+ break;
+ case "VK_RETURN":
+ key = "enter";
+ break;
+ }
+ if (aArgs) {
+ if (aArgs.shiftKey)
+ key += " shift";
+ if (aArgs.ctrlKey)
+ key += " ctrl";
+ if (aArgs.altKey)
+ key += " alt";
+ }
+ return prettyName(aNodeOrID) + " '" + key + " ' key";
+ }
+
+ this.mKey = aKey;
+ this.mArgs = aArgs ? aArgs : {};
+ this.mWindow = aArgs ? aArgs.window : null;
+}
+
+/**
+ * Tab key invoker.
+ */
+function synthTab(aNodeOrID, aCheckerOrEventSeq, aWindow)
+{
+ this.__proto__ = new synthKey(aNodeOrID, "VK_TAB",
+ { shiftKey: false, window: aWindow },
+ aCheckerOrEventSeq);
+}
+
+/**
+ * Shift tab key invoker.
+ */
+function synthShiftTab(aNodeOrID, aCheckerOrEventSeq)
+{
+ this.__proto__ = new synthKey(aNodeOrID, "VK_TAB", { shiftKey: true },
+ aCheckerOrEventSeq);
+}
+
+/**
+ * Escape key invoker.
+ */
+function synthEscapeKey(aNodeOrID, aCheckerOrEventSeq)
+{
+ this.__proto__ = new synthKey(aNodeOrID, "VK_ESCAPE", null,
+ aCheckerOrEventSeq);
+}
+
+/**
+ * Down arrow key invoker.
+ */
+function synthDownKey(aNodeOrID, aCheckerOrEventSeq, aArgs)
+{
+ this.__proto__ = new synthKey(aNodeOrID, "VK_DOWN", aArgs,
+ aCheckerOrEventSeq);
+}
+
+/**
+ * Up arrow key invoker.
+ */
+function synthUpKey(aNodeOrID, aCheckerOrEventSeq, aArgs)
+{
+ this.__proto__ = new synthKey(aNodeOrID, "VK_UP", aArgs,
+ aCheckerOrEventSeq);
+}
+
+/**
+ * Left arrow key invoker.
+ */
+function synthLeftKey(aNodeOrID, aCheckerOrEventSeq, aArgs)
+{
+ this.__proto__ = new synthKey(aNodeOrID, "VK_LEFT", aArgs, aCheckerOrEventSeq);
+}
+
+/**
+ * Right arrow key invoker.
+ */
+function synthRightKey(aNodeOrID, aCheckerOrEventSeq, aArgs)
+{
+ this.__proto__ = new synthKey(aNodeOrID, "VK_RIGHT", aArgs, aCheckerOrEventSeq);
+}
+
+/**
+ * Home key invoker.
+ */
+function synthHomeKey(aNodeOrID, aCheckerOrEventSeq)
+{
+ this.__proto__ = new synthKey(aNodeOrID, "VK_HOME", null, aCheckerOrEventSeq);
+}
+
+/**
+ * End key invoker.
+ */
+function synthEndKey(aNodeOrID, aCheckerOrEventSeq)
+{
+ this.__proto__ = new synthKey(aNodeOrID, "VK_END", null, aCheckerOrEventSeq);
+}
+
+/**
+ * Enter key invoker
+ */
+function synthEnterKey(aID, aCheckerOrEventSeq)
+{
+ this.__proto__ = new synthKey(aID, "VK_RETURN", null, aCheckerOrEventSeq);
+}
+
+/**
+ * Synth alt + down arrow to open combobox.
+ */
+function synthOpenComboboxKey(aID, aCheckerOrEventSeq)
+{
+ this.__proto__ = new synthDownKey(aID, aCheckerOrEventSeq, { altKey: true });
+
+ this.getID = function synthOpenComboboxKey_getID()
+ {
+ return "open combobox (atl + down arrow) " + prettyName(aID);
+ }
+}
+
+/**
+ * Focus invoker.
+ */
+function synthFocus(aNodeOrID, aCheckerOrEventSeq)
+{
+ var checkerOfEventSeq =
+ aCheckerOrEventSeq ? aCheckerOrEventSeq : new focusChecker(aNodeOrID);
+ this.__proto__ = new synthAction(aNodeOrID, checkerOfEventSeq);
+
+ this.invoke = function synthFocus_invoke()
+ {
+ if (this.DOMNode instanceof Components.interfaces.nsIDOMNSEditableElement &&
+ this.DOMNode.editor ||
+ this.DOMNode instanceof Components.interfaces.nsIDOMXULTextBoxElement) {
+ this.DOMNode.selectionStart = this.DOMNode.selectionEnd = this.DOMNode.value.length;
+ }
+ this.DOMNode.focus();
+ }
+
+ this.getID = function synthFocus_getID()
+ {
+ return prettyName(aNodeOrID) + " focus";
+ }
+}
+
+/**
+ * Focus invoker. Focus the HTML body of content document of iframe.
+ */
+function synthFocusOnFrame(aNodeOrID, aCheckerOrEventSeq)
+{
+ var frameDoc = getNode(aNodeOrID).contentDocument;
+ var checkerOrEventSeq =
+ aCheckerOrEventSeq ? aCheckerOrEventSeq : new focusChecker(frameDoc);
+ this.__proto__ = new synthAction(frameDoc, checkerOrEventSeq);
+
+ this.invoke = function synthFocus_invoke()
+ {
+ this.DOMNode.body.focus();
+ }
+
+ this.getID = function synthFocus_getID()
+ {
+ return prettyName(aNodeOrID) + " frame document focus";
+ }
+}
+
+/**
+ * Change the current item when the widget doesn't have a focus.
+ */
+function changeCurrentItem(aID, aItemID)
+{
+ this.eventSeq = [ new nofocusChecker() ];
+
+ this.invoke = function changeCurrentItem_invoke()
+ {
+ var controlNode = getNode(aID);
+ var itemNode = getNode(aItemID);
+
+ // HTML
+ if (controlNode.localName == "input") {
+ if (controlNode.checked)
+ this.reportError();
+
+ controlNode.checked = true;
+ return;
+ }
+
+ if (controlNode.localName == "select") {
+ if (controlNode.selectedIndex == itemNode.index)
+ this.reportError();
+
+ controlNode.selectedIndex = itemNode.index;
+ return;
+ }
+
+ // XUL
+ if (controlNode.localName == "tree") {
+ if (controlNode.currentIndex == aItemID)
+ this.reportError();
+
+ controlNode.currentIndex = aItemID;
+ return;
+ }
+
+ if (controlNode.localName == "menulist") {
+ if (controlNode.selectedItem == itemNode)
+ this.reportError();
+
+ controlNode.selectedItem = itemNode;
+ return;
+ }
+
+ if (controlNode.currentItem == itemNode)
+ ok(false, "Error in test: proposed current item is already current" + prettyName(aID));
+
+ controlNode.currentItem = itemNode;
+ }
+
+ this.getID = function changeCurrentItem_getID()
+ {
+ return "current item change for " + prettyName(aID);
+ }
+
+ this.reportError = function changeCurrentItem_reportError()
+ {
+ ok(false,
+ "Error in test: proposed current item '" + aItemID + "' is already current");
+ }
+}
+
+/**
+ * Toggle top menu invoker.
+ */
+function toggleTopMenu(aID, aCheckerOrEventSeq)
+{
+ this.__proto__ = new synthKey(aID, "VK_ALT", null,
+ aCheckerOrEventSeq);
+
+ this.getID = function toggleTopMenu_getID()
+ {
+ return "toggle top menu on " + prettyName(aID);
+ }
+}
+
+/**
+ * Context menu invoker.
+ */
+function synthContextMenu(aID, aCheckerOrEventSeq)
+{
+ this.__proto__ = new synthClick(aID, aCheckerOrEventSeq,
+ { button: 0, type: "contextmenu" });
+
+ this.getID = function synthContextMenu_getID()
+ {
+ return "context menu on " + prettyName(aID);
+ }
+}
+
+/**
+ * Open combobox, autocomplete and etc popup, check expandable states.
+ */
+function openCombobox(aComboboxID)
+{
+ this.eventSeq = [
+ new stateChangeChecker(STATE_EXPANDED, false, true, aComboboxID)
+ ];
+
+ this.invoke = function openCombobox_invoke()
+ {
+ getNode(aComboboxID).focus();
+ synthesizeKey("VK_DOWN", { altKey: true });
+ }
+
+ this.getID = function openCombobox_getID()
+ {
+ return "open combobox " + prettyName(aComboboxID);
+ }
+}
+
+/**
+ * Close combobox, autocomplete and etc popup, check expandable states.
+ */
+function closeCombobox(aComboboxID)
+{
+ this.eventSeq = [
+ new stateChangeChecker(STATE_EXPANDED, false, false, aComboboxID)
+ ];
+
+ this.invoke = function closeCombobox_invoke()
+ {
+ synthesizeKey("VK_ESCAPE", { });
+ }
+
+ this.getID = function closeCombobox_getID()
+ {
+ return "close combobox " + prettyName(aComboboxID);
+ }
+}
+
+
+/**
+ * Select all invoker.
+ */
+function synthSelectAll(aNodeOrID, aCheckerOrEventSeq)
+{
+ this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq);
+
+ this.invoke = function synthSelectAll_invoke()
+ {
+ if (this.DOMNode instanceof Components.interfaces.nsIDOMHTMLInputElement ||
+ this.DOMNode instanceof Components.interfaces.nsIDOMXULTextBoxElement) {
+ this.DOMNode.select();
+
+ } else {
+ window.getSelection().selectAllChildren(this.DOMNode);
+ }
+ }
+
+ this.getID = function synthSelectAll_getID()
+ {
+ return aNodeOrID + " selectall";
+ }
+}
+
+/**
+ * Move the caret to the end of line.
+ */
+function moveToLineEnd(aID, aCaretOffset)
+{
+ if (MAC) {
+ this.__proto__ = new synthKey(aID, "VK_RIGHT", { metaKey: true },
+ new caretMoveChecker(aCaretOffset, aID));
+ } else {
+ this.__proto__ = new synthEndKey(aID,
+ new caretMoveChecker(aCaretOffset, aID));
+ }
+
+ this.getID = function moveToLineEnd_getID()
+ {
+ return "move to line end in " + prettyName(aID);
+ }
+}
+
+/**
+ * Move the caret to the end of previous line if any.
+ */
+function moveToPrevLineEnd(aID, aCaretOffset)
+{
+ this.__proto__ = new synthAction(aID, new caretMoveChecker(aCaretOffset, aID));
+
+ this.invoke = function moveToPrevLineEnd_invoke()
+ {
+ synthesizeKey("VK_UP", { });
+
+ if (MAC)
+ synthesizeKey("VK_RIGHT", { metaKey: true });
+ else
+ synthesizeKey("VK_END", { });
+ }
+
+ this.getID = function moveToPrevLineEnd_getID()
+ {
+ return "move to previous line end in " + prettyName(aID);
+ }
+}
+
+/**
+ * Move the caret to begining of the line.
+ */
+function moveToLineStart(aID, aCaretOffset)
+{
+ if (MAC) {
+ this.__proto__ = new synthKey(aID, "VK_LEFT", { metaKey: true },
+ new caretMoveChecker(aCaretOffset, aID));
+ } else {
+ this.__proto__ = new synthHomeKey(aID,
+ new caretMoveChecker(aCaretOffset, aID));
+ }
+
+ this.getID = function moveToLineEnd_getID()
+ {
+ return "move to line start in " + prettyName(aID);
+ }
+}
+
+/**
+ * Move the caret to begining of the text.
+ */
+function moveToTextStart(aID)
+{
+ if (MAC) {
+ this.__proto__ = new synthKey(aID, "VK_UP", { metaKey: true },
+ new caretMoveChecker(0, aID));
+ } else {
+ this.__proto__ = new synthKey(aID, "VK_HOME", { ctrlKey: true },
+ new caretMoveChecker(0, aID));
+ }
+
+ this.getID = function moveToTextStart_getID()
+ {
+ return "move to text start in " + prettyName(aID);
+ }
+}
+
+/**
+ * Move the caret in text accessible.
+ */
+function moveCaretToDOMPoint(aID, aDOMPointNodeID, aDOMPointOffset,
+ aExpectedOffset, aFocusTargetID,
+ aCheckFunc)
+{
+ this.target = getAccessible(aID, [nsIAccessibleText]);
+ this.DOMPointNode = getNode(aDOMPointNodeID);
+ this.focus = aFocusTargetID ? getAccessible(aFocusTargetID) : null;
+ this.focusNode = this.focus ? this.focus.DOMNode : null;
+
+ this.invoke = function moveCaretToDOMPoint_invoke()
+ {
+ if (this.focusNode)
+ this.focusNode.focus();
+
+ var selection = this.DOMPointNode.ownerDocument.defaultView.getSelection();
+ var selRange = selection.getRangeAt(0);
+ selRange.setStart(this.DOMPointNode, aDOMPointOffset);
+ selRange.collapse(true);
+
+ selection.removeRange(selRange);
+ selection.addRange(selRange);
+ }
+
+ this.getID = function moveCaretToDOMPoint_getID()
+ {
+ return "Set caret on " + prettyName(aID) + " at point: " +
+ prettyName(aDOMPointNodeID) + " node with offset " + aDOMPointOffset;
+ }
+
+ this.finalCheck = function moveCaretToDOMPoint_finalCheck()
+ {
+ if (aCheckFunc)
+ aCheckFunc.call();
+ }
+
+ this.eventSeq = [
+ new caretMoveChecker(aExpectedOffset, this.target)
+ ];
+
+ if (this.focus)
+ this.eventSeq.push(new asyncInvokerChecker(EVENT_FOCUS, this.focus));
+}
+
+/**
+ * Set caret offset in text accessible.
+ */
+function setCaretOffset(aID, aOffset, aFocusTargetID)
+{
+ this.target = getAccessible(aID, [nsIAccessibleText]);
+ this.offset = aOffset == -1 ? this.target.characterCount: aOffset;
+ this.focus = aFocusTargetID ? getAccessible(aFocusTargetID) : null;
+
+ this.invoke = function setCaretOffset_invoke()
+ {
+ this.target.caretOffset = this.offset;
+ }
+
+ this.getID = function setCaretOffset_getID()
+ {
+ return "Set caretOffset on " + prettyName(aID) + " at " + this.offset;
+ }
+
+ this.eventSeq = [
+ new caretMoveChecker(this.offset, this.target)
+ ];
+
+ if (this.focus)
+ this.eventSeq.push(new asyncInvokerChecker(EVENT_FOCUS, this.focus));
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Event queue checkers
+
+/**
+ * Common invoker checker (see eventSeq of eventQueue).
+ */
+function invokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg, aIsAsync)
+{
+ this.type = aEventType;
+ this.async = aIsAsync;
+
+ this.__defineGetter__("target", invokerChecker_targetGetter);
+ this.__defineSetter__("target", invokerChecker_targetSetter);
+
+ // implementation details
+ function invokerChecker_targetGetter()
+ {
+ if (typeof this.mTarget == "function")
+ return this.mTarget.call(null, this.mTargetFuncArg);
+ if (typeof this.mTarget == "string")
+ return getNode(this.mTarget);
+
+ return this.mTarget;
+ }
+
+ function invokerChecker_targetSetter(aValue)
+ {
+ this.mTarget = aValue;
+ return this.mTarget;
+ }
+
+ this.__defineGetter__("targetDescr", invokerChecker_targetDescrGetter);
+
+ function invokerChecker_targetDescrGetter()
+ {
+ if (typeof this.mTarget == "function")
+ return this.mTarget.name + ", arg: " + this.mTargetFuncArg;
+
+ return prettyName(this.mTarget);
+ }
+
+ this.mTarget = aTargetOrFunc;
+ this.mTargetFuncArg = aTargetFuncArg;
+}
+
+/**
+ * event checker that forces preceeding async events to happen before this
+ * checker.
+ */
+function orderChecker()
+{
+ // XXX it doesn't actually work to inherit from invokerChecker, but maybe we
+ // should fix that?
+ // this.__proto__ = new invokerChecker(null, null, null, false);
+}
+
+/**
+ * Generic invoker checker for todo events.
+ */
+function todo_invokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg)
+{
+ this.__proto__ = new invokerChecker(aEventType, aTargetOrFunc,
+ aTargetFuncArg, true);
+ this.todo = true;
+}
+
+/**
+ * Generic invoker checker for unexpected events.
+ */
+function unexpectedInvokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg)
+{
+ this.__proto__ = new invokerChecker(aEventType, aTargetOrFunc,
+ aTargetFuncArg, true);
+
+ this.unexpected = true;
+}
+
+/**
+ * Common invoker checker for async events.
+ */
+function asyncInvokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg)
+{
+ this.__proto__ = new invokerChecker(aEventType, aTargetOrFunc,
+ aTargetFuncArg, true);
+}
+
+function focusChecker(aTargetOrFunc, aTargetFuncArg)
+{
+ this.__proto__ = new invokerChecker(EVENT_FOCUS, aTargetOrFunc,
+ aTargetFuncArg, false);
+
+ this.unique = true; // focus event must be unique for invoker action
+
+ this.check = function focusChecker_check(aEvent)
+ {
+ testStates(aEvent.accessible, STATE_FOCUSED);
+ }
+}
+
+function nofocusChecker(aID)
+{
+ this.__proto__ = new focusChecker(aID);
+ this.unexpected = true;
+}
+
+/**
+ * Text inserted/removed events checker.
+ * @param aFromUser [in, optional] kNotFromUserInput or kFromUserInput
+ */
+function textChangeChecker(aID, aStart, aEnd, aTextOrFunc, aIsInserted, aFromUser, aAsync)
+{
+ this.target = getNode(aID);
+ this.type = aIsInserted ? EVENT_TEXT_INSERTED : EVENT_TEXT_REMOVED;
+ this.startOffset = aStart;
+ this.endOffset = aEnd;
+ this.textOrFunc = aTextOrFunc;
+ this.async = aAsync;
+
+ this.match = function stextChangeChecker_match(aEvent)
+ {
+ if (!(aEvent instanceof nsIAccessibleTextChangeEvent) ||
+ aEvent.accessible !== getAccessible(this.target)) {
+ return false;
+ }
+
+ let tcEvent = aEvent.QueryInterface(nsIAccessibleTextChangeEvent);
+ let modifiedText = (typeof this.textOrFunc === "function") ?
+ this.textOrFunc() : this.textOrFunc;
+ return modifiedText === tcEvent.modifiedText;
+ };
+
+ this.check = function textChangeChecker_check(aEvent)
+ {
+ aEvent.QueryInterface(nsIAccessibleTextChangeEvent);
+
+ var modifiedText = (typeof this.textOrFunc == "function") ?
+ this.textOrFunc() : this.textOrFunc;
+ var modifiedTextLen =
+ (this.endOffset == -1) ? modifiedText.length : aEnd - aStart;
+
+ is(aEvent.start, this.startOffset,
+ "Wrong start offset for " + prettyName(aID));
+ is(aEvent.length, modifiedTextLen, "Wrong length for " + prettyName(aID));
+ var changeInfo = (aIsInserted ? "inserted" : "removed");
+ is(aEvent.isInserted, aIsInserted,
+ "Text was " + changeInfo + " for " + prettyName(aID));
+ is(aEvent.modifiedText, modifiedText,
+ "Wrong " + changeInfo + " text for " + prettyName(aID));
+ if (typeof aFromUser != "undefined")
+ is(aEvent.isFromUserInput, aFromUser,
+ "wrong value of isFromUserInput() for " + prettyName(aID));
+ }
+}
+
+/**
+ * Caret move events checker.
+ */
+function caretMoveChecker(aCaretOffset, aTargetOrFunc, aTargetFuncArg,
+ aIsAsync)
+{
+ this.__proto__ = new invokerChecker(EVENT_TEXT_CARET_MOVED,
+ aTargetOrFunc, aTargetFuncArg, aIsAsync);
+
+ this.check = function caretMoveChecker_check(aEvent)
+ {
+ is(aEvent.QueryInterface(nsIAccessibleCaretMoveEvent).caretOffset,
+ aCaretOffset,
+ "Wrong caret offset for " + prettyName(aEvent.accessible));
+ }
+}
+
+function asyncCaretMoveChecker(aCaretOffset, aTargetOrFunc, aTargetFuncArg)
+{
+ this.__proto__ = new caretMoveChecker(aCaretOffset, aTargetOrFunc,
+ aTargetFuncArg, true);
+}
+
+/**
+ * Text selection change checker.
+ */
+function textSelectionChecker(aID, aStartOffset, aEndOffset)
+{
+ this.__proto__ = new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID);
+
+ this.check = function textSelectionChecker_check(aEvent)
+ {
+ if (aStartOffset == aEndOffset) {
+ ok(true, "Collapsed selection triggered text selection change event.");
+ } else {
+ testTextGetSelection(aID, aStartOffset, aEndOffset, 0);
+ }
+ }
+}
+
+/**
+ * Object attribute changed checker
+ */
+function objAttrChangedChecker(aID, aAttr)
+{
+ this.__proto__ = new invokerChecker(EVENT_OBJECT_ATTRIBUTE_CHANGED, aID);
+
+ this.check = function objAttrChangedChecker_check(aEvent)
+ {
+ var event = null;
+ try {
+ var event = aEvent.QueryInterface(
+ nsIAccessibleObjectAttributeChangedEvent);
+ } catch (e) {
+ ok(false, "Object attribute changed event was expected");
+ }
+
+ if (!event) {
+ return;
+ }
+
+ is(event.changedAttribute.toString(), aAttr,
+ "Wrong attribute name of the object attribute changed event.");
+ };
+
+ this.match = function objAttrChangedChecker_match(aEvent)
+ {
+ if (aEvent instanceof nsIAccessibleObjectAttributeChangedEvent) {
+ var scEvent = aEvent.QueryInterface(
+ nsIAccessibleObjectAttributeChangedEvent);
+ return (aEvent.accessible == getAccessible(this.target)) &&
+ (scEvent.changedAttribute.toString() == aAttr);
+ }
+ return false;
+ };
+}
+
+/**
+ * State change checker.
+ */
+function stateChangeChecker(aState, aIsExtraState, aIsEnabled,
+ aTargetOrFunc, aTargetFuncArg, aIsAsync,
+ aSkipCurrentStateCheck)
+{
+ this.__proto__ = new invokerChecker(EVENT_STATE_CHANGE, aTargetOrFunc,
+ aTargetFuncArg, aIsAsync);
+
+ this.check = function stateChangeChecker_check(aEvent)
+ {
+ var event = null;
+ try {
+ var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
+ } catch (e) {
+ ok(false, "State change event was expected");
+ }
+
+ if (!event)
+ return;
+
+ is(event.isExtraState, aIsExtraState,
+ "Wrong extra state bit of the statechange event.");
+ isState(event.state, aState, aIsExtraState,
+ "Wrong state of the statechange event.");
+ is(event.isEnabled, aIsEnabled,
+ "Wrong state of statechange event state");
+
+ if (aSkipCurrentStateCheck) {
+ todo(false, "State checking was skipped!");
+ return;
+ }
+
+ var state = aIsEnabled ? (aIsExtraState ? 0 : aState) : 0;
+ var extraState = aIsEnabled ? (aIsExtraState ? aState : 0) : 0;
+ var unxpdState = aIsEnabled ? 0 : (aIsExtraState ? 0 : aState);
+ var unxpdExtraState = aIsEnabled ? 0 : (aIsExtraState ? aState : 0);
+ testStates(event.accessible, state, extraState, unxpdState, unxpdExtraState);
+ }
+
+ this.match = function stateChangeChecker_match(aEvent)
+ {
+ if (aEvent instanceof nsIAccessibleStateChangeEvent) {
+ var scEvent = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
+ return (aEvent.accessible == getAccessible(this.target)) &&
+ (scEvent.state == aState);
+ }
+ return false;
+ }
+}
+
+function asyncStateChangeChecker(aState, aIsExtraState, aIsEnabled,
+ aTargetOrFunc, aTargetFuncArg)
+{
+ this.__proto__ = new stateChangeChecker(aState, aIsExtraState, aIsEnabled,
+ aTargetOrFunc, aTargetFuncArg, true);
+}
+
+/**
+ * Expanded state change checker.
+ */
+function expandedStateChecker(aIsEnabled, aTargetOrFunc, aTargetFuncArg)
+{
+ this.__proto__ = new invokerChecker(EVENT_STATE_CHANGE, aTargetOrFunc,
+ aTargetFuncArg);
+
+ this.check = function expandedStateChecker_check(aEvent)
+ {
+ var event = null;
+ try {
+ var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
+ } catch (e) {
+ ok(false, "State change event was expected");
+ }
+
+ if (!event)
+ return;
+
+ is(event.state, STATE_EXPANDED, "Wrong state of the statechange event.");
+ is(event.isExtraState, false,
+ "Wrong extra state bit of the statechange event.");
+ is(event.isEnabled, aIsEnabled,
+ "Wrong state of statechange event state");
+
+ testStates(event.accessible,
+ (aIsEnabled ? STATE_EXPANDED : STATE_COLLAPSED));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Event sequances (array of predefined checkers)
+
+/**
+ * Event seq for single selection change.
+ */
+function selChangeSeq(aUnselectedID, aSelectedID)
+{
+ if (!aUnselectedID) {
+ return [
+ new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
+ new invokerChecker(EVENT_SELECTION, aSelectedID)
+ ];
+ }
+
+ // Return two possible scenarios: depending on widget type when selection is
+ // moved the the order of items that get selected and unselected may vary.
+ return [
+ [
+ new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID),
+ new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
+ new invokerChecker(EVENT_SELECTION, aSelectedID)
+ ],
+ [
+ new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
+ new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID),
+ new invokerChecker(EVENT_SELECTION, aSelectedID)
+ ]
+ ];
+}
+
+/**
+ * Event seq for item removed form the selection.
+ */
+function selRemoveSeq(aUnselectedID)
+{
+ return [
+ new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID),
+ new invokerChecker(EVENT_SELECTION_REMOVE, aUnselectedID)
+ ];
+}
+
+/**
+ * Event seq for item added to the selection.
+ */
+function selAddSeq(aSelectedID)
+{
+ return [
+ new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
+ new invokerChecker(EVENT_SELECTION_ADD, aSelectedID)
+ ];
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Private implementation details.
+////////////////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////////////////
+// General
+
+var gA11yEventListeners = {};
+var gA11yEventApplicantsCount = 0;
+
+var gA11yEventObserver =
+{
+ observe: function observe(aSubject, aTopic, aData)
+ {
+ if (aTopic != "accessible-event")
+ return;
+
+ var event;
+ try {
+ event = aSubject.QueryInterface(nsIAccessibleEvent);
+ } catch (ex) {
+ // After a test is aborted (i.e. timed out by the harness), this exception is soon triggered.
+ // Remove the leftover observer, otherwise it "leaks" to all the following tests.
+ Services.obs.removeObserver(this, "accessible-event");
+ // Forward the exception, with added explanation.
+ throw "[accessible/events.js, gA11yEventObserver.observe] This is expected if a previous test has been aborted... Initial exception was: [ " + ex + " ]";
+ }
+ var listenersArray = gA11yEventListeners[event.eventType];
+
+ var eventFromDumpArea = false;
+ if (gLogger.isEnabled()) { // debug stuff
+ eventFromDumpArea = true;
+
+ var target = event.DOMNode;
+ var dumpElm = gA11yEventDumpID ?
+ document.getElementById(gA11yEventDumpID) : null;
+
+ if (dumpElm) {
+ var parent = target;
+ while (parent && parent != dumpElm)
+ parent = parent.parentNode;
+ }
+
+ if (!dumpElm || parent != dumpElm) {
+ var type = eventTypeToString(event.eventType);
+ var info = "Event type: " + type;
+
+ if (event instanceof nsIAccessibleStateChangeEvent) {
+ var stateStr = statesToString(event.isExtraState ? 0 : event.state,
+ event.isExtraState ? event.state : 0);
+ info += ", state: " + stateStr + ", is enabled: " + event.isEnabled;
+
+ } else if (event instanceof nsIAccessibleTextChangeEvent) {
+ info += ", start: " + event.start + ", length: " + event.length +
+ ", " + (event.isInserted ? "inserted" : "removed") +
+ " text: " + event.modifiedText;
+ }
+
+ info += ". Target: " + prettyName(event.accessible);
+
+ if (listenersArray)
+ info += ". Listeners count: " + listenersArray.length;
+
+ if (gLogger.hasFeature("parentchain:" + type)) {
+ info += "\nParent chain:\n";
+ var acc = event.accessible;
+ while (acc) {
+ info += " " + prettyName(acc) + "\n";
+ acc = acc.parent;
+ }
+ }
+
+ eventFromDumpArea = false;
+ gLogger.log(info);
+ }
+ }
+
+ // Do not notify listeners if event is result of event log changes.
+ if (!listenersArray || eventFromDumpArea)
+ return;
+
+ for (var index = 0; index < listenersArray.length; index++)
+ listenersArray[index].handleEvent(event);
+ }
+};
+
+function listenA11yEvents(aStartToListen)
+{
+ if (aStartToListen) {
+ // Add observer when adding the first applicant only.
+ if (!(gA11yEventApplicantsCount++))
+ Services.obs.addObserver(gA11yEventObserver, "accessible-event", false);
+ } else {
+ // Remove observer when there are no more applicants only.
+ // '< 0' case should not happen, but just in case: removeObserver() will throw.
+ if (--gA11yEventApplicantsCount <= 0)
+ Services.obs.removeObserver(gA11yEventObserver, "accessible-event");
+ }
+}
+
+function addA11yEventListener(aEventType, aEventHandler)
+{
+ if (!(aEventType in gA11yEventListeners))
+ gA11yEventListeners[aEventType] = new Array();
+
+ var listenersArray = gA11yEventListeners[aEventType];
+ var index = listenersArray.indexOf(aEventHandler);
+ if (index == -1)
+ listenersArray.push(aEventHandler);
+}
+
+function removeA11yEventListener(aEventType, aEventHandler)
+{
+ var listenersArray = gA11yEventListeners[aEventType];
+ if (!listenersArray)
+ return false;
+
+ var index = listenersArray.indexOf(aEventHandler);
+ if (index == -1)
+ return false;
+
+ listenersArray.splice(index, 1);
+
+ if (!listenersArray.length) {
+ gA11yEventListeners[aEventType] = null;
+ delete gA11yEventListeners[aEventType];
+ }
+
+ return true;
+}
+
+/**
+ * Used to dump debug information.
+ */
+var gLogger =
+{
+ /**
+ * Return true if dump is enabled.
+ */
+ isEnabled: function debugOutput_isEnabled()
+ {
+ return gA11yEventDumpID || gA11yEventDumpToConsole ||
+ gA11yEventDumpToAppConsole;
+ },
+
+ /**
+ * Dump information into DOM and console if applicable.
+ */
+ log: function logger_log(aMsg)
+ {
+ this.logToConsole(aMsg);
+ this.logToAppConsole(aMsg);
+ this.logToDOM(aMsg);
+ },
+
+ /**
+ * Log message to DOM.
+ *
+ * @param aMsg [in] the primary message
+ * @param aHasIndent [in, optional] if specified the message has an indent
+ * @param aPreEmphText [in, optional] the text is colored and appended prior
+ * primary message
+ */
+ logToDOM: function logger_logToDOM(aMsg, aHasIndent, aPreEmphText)
+ {
+ if (gA11yEventDumpID == "")
+ return;
+
+ var dumpElm = document.getElementById(gA11yEventDumpID);
+ if (!dumpElm) {
+ ok(false,
+ "No dump element '" + gA11yEventDumpID + "' within the document!");
+ return;
+ }
+
+ var containerTagName = document instanceof nsIDOMHTMLDocument ?
+ "div" : "description";
+
+ var container = document.createElement(containerTagName);
+ if (aHasIndent)
+ container.setAttribute("style", "padding-left: 10px;");
+
+ if (aPreEmphText) {
+ var inlineTagName = document instanceof nsIDOMHTMLDocument ?
+ "span" : "description";
+ var emphElm = document.createElement(inlineTagName);
+ emphElm.setAttribute("style", "color: blue;");
+ emphElm.textContent = aPreEmphText;
+
+ container.appendChild(emphElm);
+ }
+
+ var textNode = document.createTextNode(aMsg);
+ container.appendChild(textNode);
+
+ dumpElm.appendChild(container);
+ },
+
+ /**
+ * Log message to console.
+ */
+ logToConsole: function logger_logToConsole(aMsg)
+ {
+ if (gA11yEventDumpToConsole)
+ dump("\n" + aMsg + "\n");
+ },
+
+ /**
+ * Log message to error console.
+ */
+ logToAppConsole: function logger_logToAppConsole(aMsg)
+ {
+ if (gA11yEventDumpToAppConsole)
+ Services.console.logStringMessage("events: " + aMsg);
+ },
+
+ /**
+ * Return true if logging feature is enabled.
+ */
+ hasFeature: function logger_hasFeature(aFeature)
+ {
+ var startIdx = gA11yEventDumpFeature.indexOf(aFeature);
+ if (startIdx == - 1)
+ return false;
+
+ var endIdx = startIdx + aFeature.length;
+ return endIdx == gA11yEventDumpFeature.length ||
+ gA11yEventDumpFeature[endIdx] == ";";
+ }
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Sequence
+
+/**
+ * Base class of sequence item.
+ */
+function sequenceItem(aProcessor, aEventType, aTarget, aItemID)
+{
+ // private
+
+ this.startProcess = function sequenceItem_startProcess()
+ {
+ this.queue.invoke();
+ }
+
+ var item = this;
+
+ this.queue = new eventQueue();
+ this.queue.onFinish = function()
+ {
+ aProcessor.onProcessed();
+ return DO_NOT_FINISH_TEST;
+ }
+
+ var invoker = {
+ invoke: function invoker_invoke() {
+ return aProcessor.process();
+ },
+ getID: function invoker_getID()
+ {
+ return aItemID;
+ },
+ eventSeq: [ new invokerChecker(aEventType, aTarget) ]
+ };
+
+ this.queue.push(invoker);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Event queue invokers
+
+/**
+ * Invoker base class for prepare an action.
+ */
+function synthAction(aNodeOrID, aEventsObj)
+{
+ this.DOMNode = getNode(aNodeOrID);
+
+ if (aEventsObj) {
+ var scenarios = null;
+ if (aEventsObj instanceof Array) {
+ if (aEventsObj[0] instanceof Array)
+ scenarios = aEventsObj; // scenarios
+ else
+ scenarios = [ aEventsObj ]; // event sequance
+ } else {
+ scenarios = [ [ aEventsObj ] ]; // a single checker object
+ }
+
+ for (var i = 0; i < scenarios.length; i++)
+ defineScenario(this, scenarios[i]);
+ }
+
+ this.getID = function synthAction_getID()
+ { return prettyName(aNodeOrID) + " action"; }
+}
diff --git a/accessible/tests/mochitest/events/a11y.ini b/accessible/tests/mochitest/events/a11y.ini
new file mode 100644
index 000000000..4ea7c9d10
--- /dev/null
+++ b/accessible/tests/mochitest/events/a11y.ini
@@ -0,0 +1,67 @@
+[DEFAULT]
+support-files =
+ docload_wnd.html
+ focus.html
+ scroll.html
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/letters.gif
+
+[test_aria_alert.html]
+[test_aria_menu.html]
+[test_aria_objattr.html]
+[test_aria_owns.html]
+[test_aria_statechange.html]
+[test_attrs.html]
+[test_bug1322593.html]
+[test_bug1322593-2.html]
+[test_caretmove.html]
+[test_caretmove.xul]
+[test_coalescence.html]
+[test_contextmenu.html]
+[test_descrchange.html]
+[test_docload.html]
+[test_docload.xul]
+skip-if = buildapp == 'mulet'
+[test_docload_aria.html]
+[test_dragndrop.html]
+[test_flush.html]
+[test_focus_aria_activedescendant.html]
+[test_focus_autocomplete.xul]
+# Disabled on Linux and Windows due to frequent failures - bug 695019, bug 890795
+skip-if = os == 'win' || os == 'linux'
+[test_focus_browserui.xul]
+[test_focus_canvas.html]
+[test_focus_contextmenu.xul]
+[test_focus_controls.html]
+[test_focus_dialog.html]
+[test_focus_doc.html]
+[test_focus_general.html]
+[test_focus_general.xul]
+[test_focus_listcontrols.xul]
+[test_focus_menu.xul]
+[test_focus_name.html]
+[test_focus_selects.html]
+[test_focus_tabbox.xul]
+[test_focus_tree.xul]
+[test_fromUserInput.html]
+[test_label.xul]
+[test_menu.xul]
+[test_mutation.html]
+[test_mutation.xhtml]
+[test_namechange.xul]
+[test_namechange.html]
+[test_scroll.xul]
+[test_scroll_caret.xul]
+[test_selection.html]
+skip-if = buildapp == 'mulet' || os == 'mac'
+[test_selection.xul]
+skip-if = os == 'mac'
+[test_selection_aria.html]
+[test_statechange.html]
+[test_text.html]
+[test_text_alg.html]
+[test_textattrchange.html]
+[test_textselchange.html]
+[test_tree.xul]
+[test_valuechange.html]
+skip-if = os == 'mac'
diff --git a/accessible/tests/mochitest/events/docload_wnd.html b/accessible/tests/mochitest/events/docload_wnd.html
new file mode 100644
index 000000000..86ddfac5e
--- /dev/null
+++ b/accessible/tests/mochitest/events/docload_wnd.html
@@ -0,0 +1,39 @@
+<html>
+<head>
+ <title>Accessible events testing for document</title>
+ <script>
+ const STATE_BUSY = Components.interfaces.nsIAccessibleStates.STATE_BUSY;
+
+ var gService = null;
+ function waitForDocLoad()
+ {
+ if (!gService) {
+ gService = Components.classes["@mozilla.org/accessibilityService;1"].
+ getService(Components.interfaces.nsIAccessibilityService);
+ }
+
+ var accDoc = gService.getAccessibleFor(document);
+
+ var state = {};
+ accDoc.getState(state, {});
+ if (state.value & STATE_BUSY) {
+ window.setTimeout(waitForDocLoad, 0);
+ return;
+ }
+
+ hideIFrame();
+ }
+
+ function hideIFrame()
+ {
+ var iframe = document.getElementById("iframe");
+ gService.getAccessibleFor(iframe.contentDocument);
+ iframe.style.display = 'none';
+ }
+ </script>
+</head>
+
+<body onload="waitForDocLoad();">
+ <iframe id="iframe"></iframe>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/focus.html b/accessible/tests/mochitest/events/focus.html
new file mode 100644
index 000000000..ab055df82
--- /dev/null
+++ b/accessible/tests/mochitest/events/focus.html
@@ -0,0 +1,10 @@
+<html>
+
+<head>
+ <title>editable document</title>
+</head>
+
+<body contentEditable="true">
+ editable document
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/scroll.html b/accessible/tests/mochitest/events/scroll.html
new file mode 100644
index 000000000..562e0a382
--- /dev/null
+++ b/accessible/tests/mochitest/events/scroll.html
@@ -0,0 +1,181 @@
+<html>
+
+<head>
+ <title>nsIAccessible actions testing for anchors</title>
+</head>
+
+<body>
+ <p>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ </p>
+ <a name="link1">link1</a>
+
+ <p style="color: blue">
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ </p>
+
+ <h1 id="heading_1">heading 1</h1>
+ <p style="color: blue">
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ text text text text text text text text text text text text text text <br>
+ </p>
+</body>
+<html>
diff --git a/accessible/tests/mochitest/events/test_aria_alert.html b/accessible/tests/mochitest/events/test_aria_alert.html
new file mode 100644
index 000000000..2dab35723
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_aria_alert.html
@@ -0,0 +1,92 @@
+<html>
+
+<head>
+ <title>ARIA alert event testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function showAlert(aID)
+ {
+ this.DOMNode = document.createElement("div");
+
+ this.invoke = function showAlert_invoke()
+ {
+ this.DOMNode.setAttribute("role", "alert");
+ this.DOMNode.setAttribute("id", aID);
+ var text = document.createTextNode("alert");
+ this.DOMNode.appendChild(text);
+ document.body.appendChild(this.DOMNode);
+ };
+
+ this.getID = function showAlert_getID()
+ {
+ return "Show ARIA alert " + aID;
+ };
+ }
+
+ function changeAlert(aID)
+ {
+ this.__defineGetter__("DOMNode", function() { return getNode(aID) });
+
+ this.invoke = function changeAlert_invoke()
+ {
+ this.DOMNode.textContent = "new alert";
+ }
+
+ this.getID = function showAlert_getID()
+ {
+ return "Change ARIA alert " + aID;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ //gA11yEventDumpToConsole = true; // debuging
+ //enableLogging("tree,events,verbose");
+
+ var gQueue = null;
+ function doTests()
+ {
+ gQueue = new eventQueue(nsIAccessibleEvent.EVENT_ALERT);
+
+ gQueue.push(new showAlert("alert"));
+ gQueue.push(new changeAlert("alert"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=591199"
+ title="mochitest for bug 334386: fire alert event when ARIA alert is shown or new its children are inserted">
+ Mozilla Bug 591199
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_aria_menu.html b/accessible/tests/mochitest/events/test_aria_menu.html
new file mode 100644
index 000000000..5ac595ebf
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_aria_menu.html
@@ -0,0 +1,285 @@
+<html>
+
+<head>
+ <title>ARIA menu events testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ const kViaDisplayStyle = 0;
+ const kViaVisibilityStyle = 1;
+
+ function focusMenu(aMenuBarID, aMenuID, aActiveMenuBarID)
+ {
+ this.eventSeq = [];
+
+ if (aActiveMenuBarID) {
+ this.eventSeq.push(new invokerChecker(EVENT_MENU_END,
+ getNode(aActiveMenuBarID)));
+ }
+
+ this.eventSeq.push(new invokerChecker(EVENT_MENU_START, getNode(aMenuBarID)));
+ this.eventSeq.push(new invokerChecker(EVENT_FOCUS, getNode(aMenuID)));
+
+ this.invoke = function focusMenu_invoke()
+ {
+ getNode(aMenuID).focus();
+ }
+
+ this.getID = function focusMenu_getID()
+ {
+ return "focus menu '" + aMenuID + "'";
+ }
+ }
+
+ function showMenu(aMenuID, aParentMenuID, aHow)
+ {
+ this.menuNode = getNode(aMenuID);
+
+ // Because of aria-owns processing we may have menupopup start fired before
+ // related show event.
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, this.menuNode),
+ new invokerChecker(EVENT_REORDER, getNode(aParentMenuID)),
+ new invokerChecker(EVENT_MENUPOPUP_START, this.menuNode)
+ ];
+
+ this.invoke = function showMenu_invoke()
+ {
+ if (aHow == kViaDisplayStyle)
+ this.menuNode.style.display = "block";
+ else
+ this.menuNode.style.visibility = "visible";
+ };
+
+ this.getID = function showMenu_getID()
+ {
+ return "Show ARIA menu '" + aMenuID + "' by " +
+ (aHow == kViaDisplayStyle ? "display" : "visibility") +
+ " style tricks";
+ };
+ }
+
+ function closeMenu(aMenuID, aParentMenuID, aHow)
+ {
+ this.menuNode = getNode(aMenuID);
+ this.menu = null;
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getMenu, this),
+ new invokerChecker(EVENT_MENUPOPUP_END, getMenu, this),
+ new invokerChecker(EVENT_REORDER, getNode(aParentMenuID))
+ ];
+
+ this.invoke = function closeMenu_invoke()
+ {
+ // Store menu accessible reference while menu is still open.
+ this.menu = getAccessible(this.menuNode);
+
+ // Hide menu.
+ if (aHow == kViaDisplayStyle)
+ this.menuNode.style.display = "none";
+ else
+ this.menuNode.style.visibility = "hidden";
+ }
+
+ this.getID = function closeMenu_getID()
+ {
+ return "Close ARIA menu " + aMenuID + " by " +
+ (aHow == kViaDisplayStyle ? "display" : "visibility") +
+ " style tricks";
+ }
+
+ function getMenu(aThisObj)
+ {
+ return aThisObj.menu;
+ }
+ }
+
+ function focusInsideMenu(aMenuID, aMenuBarID)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, getNode(aMenuID))
+ ];
+
+ this.unexpectedEventSeq = [
+ new invokerChecker(EVENT_MENU_END, getNode(aMenuBarID))
+ ];
+
+ this.invoke = function focusInsideMenu_invoke()
+ {
+ getNode(aMenuID).focus();
+ }
+
+ this.getID = function focusInsideMenu_getID()
+ {
+ return "focus menu '" + aMenuID + "'";
+ }
+ }
+
+ function blurMenu(aMenuBarID)
+ {
+ var eventSeq = [
+ new invokerChecker(EVENT_MENU_END, getNode(aMenuBarID)),
+ new invokerChecker(EVENT_FOCUS, getNode("outsidemenu"))
+ ];
+
+ this.__proto__ = new synthClick("outsidemenu", eventSeq);
+
+ this.getID = function blurMenu_getID()
+ {
+ return "blur menu";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ //gA11yEventDumpToConsole = true; // debuging
+ //enableLogging("tree,events,verbose");
+
+ var gQueue = null;
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new focusMenu("menubar2", "menu-help"));
+ gQueue.push(new focusMenu("menubar", "menu-file", "menubar2"));
+ gQueue.push(new showMenu("menupopup-file", "menu-file", kViaDisplayStyle));
+ gQueue.push(new closeMenu("menupopup-file", "menu-file", kViaDisplayStyle));
+ gQueue.push(new showMenu("menupopup-edit", "menu-edit", kViaVisibilityStyle));
+ gQueue.push(new closeMenu("menupopup-edit", "menu-edit", kViaVisibilityStyle));
+ gQueue.push(new focusInsideMenu("menu-edit", "menubar"));
+ gQueue.push(new blurMenu("menubar"));
+
+ gQueue.push(new focusMenu("menubar3", "mb3-mi-outside"));
+ gQueue.push(new showMenu("mb4-menu", document, kViaDisplayStyle));
+ gQueue.push(new focusMenu("menubar4", "mb4-item1"));
+ gQueue.push(new focusMenu("menubar5", "mb5-mi"));
+
+ gQueue.push(new synthFocus("mi6"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=606207"
+ title="Dojo dropdown buttons are broken">
+ Bug 606207
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=614829"
+ title="Menupopup end event isn't fired for ARIA menus">
+ Bug 614829
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=615189"
+ title="Clean up FireAccessibleFocusEvent">
+ Bug 615189
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958"
+ title="Rework accessible focus handling">
+ Bug 673958
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=933322"
+ title="menustart/end events are missing when aria-owns makes a menu hierarchy">
+ Bug 933322
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=934460"
+ title="menustart/end events may be missed when top level menuitem is focused">
+ Bug 934460
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=970005"
+ title="infinite long loop in a11y:FocusManager::ProcessFocusEvent">
+ Bug 970005
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="menubar" role="menubar">
+ <div id="menu-file" role="menuitem" tabindex="0">
+ File
+ <div id="menupopup-file" role="menu" style="display: none;">
+ <div id="menuitem-newtab" role="menuitem" tabindex="0">New Tab</div>
+ <div id="menuitem-newwindow" role="menuitem" tabindex="0">New Window</div>
+ </div>
+ </div>
+ <div id="menu-edit" role="menuitem" tabindex="0">
+ Edit
+ <div id="menupopup-edit" role="menu" style="visibility: hidden;">
+ <div id="menuitem-undo" role="menuitem" tabindex="0">Undo</div>
+ <div id="menuitem-redo" role="menuitem" tabindex="0">Redo</div>
+ </div>
+ </div>
+ </div>
+ <div id="menubar2" role="menubar">
+ <div id="menu-help" role="menuitem" tabindex="0">
+ Help
+ <div id="menupopup-help" role="menu" style="display: none;">
+ <div id="menuitem-about" role="menuitem" tabindex="0">About</div>
+ </div>
+ </div>
+ </div>
+ <div tabindex="0" id="outsidemenu">outsidemenu</div>
+
+ <!-- aria-owns relations -->
+ <div id="menubar3" role="menubar" aria-owns="mb3-mi-outside"></div>
+ <div id="mb3-mi-outside" role="menuitem" tabindex="0">Outside</div>
+
+ <div id="menubar4" role="menubar">
+ <div id="mb4_topitem" role="menuitem" aria-haspopup="true"
+ aria-owns="mb4-menu">Item</div>
+ </div>
+ <div id="mb4-menu" role="menu" style="display:none;">
+ <div role="menuitem" id="mb4-item1" tabindex="0">Item 1.1</div>
+ <div role="menuitem" tabindex="0">Item 1.2</div>
+ </div>
+
+ <!-- focus top-level menu item having haspopup -->
+ <div id="menubar5" role="menubar">
+ <div role="menuitem" aria-haspopup="true" id="mb5-mi" tabindex="0">
+ Item
+ <div role="menu" style="display:none;">
+ <div role="menuitem" tabindex="0">Item 1.1</div>
+ <div role="menuitem" tabindex="0">Item 1.2</div>
+ </div>
+ </div>
+ </div>
+
+ <!-- other aria-owns relations -->
+ <div id="mi6" tabindex="0" role="menuitem">aria-owned item</div>
+ <div aria-owns="mi6">Obla</div>
+
+ <div id="eventdump"></div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_aria_objattr.html b/accessible/tests/mochitest/events/test_aria_objattr.html
new file mode 100644
index 000000000..5f16ba794
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_aria_objattr.html
@@ -0,0 +1,118 @@
+<html>
+
+<head>
+ <title>Accessible ARIA object attribute changes</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ /**
+ * Do tests.
+ */
+ var gQueue = null;
+ function updateAttribute(aID, aAttr, aValue)
+ {
+ this.node = getNode(aID);
+ this.accessible = getAccessible(this.node);
+
+ this.eventSeq = [
+ new objAttrChangedChecker(aID, aAttr),
+ ];
+
+ this.invoke = function updateAttribute_invoke()
+ {
+ this.node.setAttribute(aAttr, aValue);
+ };
+
+ this.getID = function updateAttribute_getID()
+ {
+ return aAttr + " for " + aID + " " + aValue;
+ };
+ }
+
+ function updateARIAHidden(aID, aIsDefined, aChildId)
+ {
+ this.__proto__ = new updateAttribute(aID, "aria-hidden",
+ aIsDefined ? "true" : "false");
+
+ this.finalCheck = function updateARIAHidden()
+ {
+ if (aIsDefined) {
+ testAttrs(aID, {"hidden" : "true"}, true);
+ testAttrs(aChildId, {"hidden" : "true"}, true);
+ } else {
+ testAbsentAttrs(aID, { "hidden": "true"});
+ testAbsentAttrs(aChildId, { "hidden": "true"});
+ }
+ }
+ }
+
+ // Debug stuff.
+ // gA11yEventDumpID = "eventdump";
+ //gA11yEventDumpToConsole = true;
+
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new updateARIAHidden("hideable", true, "hideable_child"));
+ gQueue.push(new updateARIAHidden("hideable", false, "hideable_child"));
+
+ gQueue.push(new updateAttribute("sortable", "aria-sort", "ascending"));
+
+ // For experimental ARIA extensions
+ gQueue.push(new updateAttribute("custom", "aria-blah", "true"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=581096"
+ title="Add support for aria-hidden">
+ Mozilla Bug 581096
+ </a>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=640707"
+ title="Add event support for aria-sort">
+ Mozilla Bug 640707
+ </a>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=640707"
+ title="Expand support for aria attribute change events">
+ Mozilla Bug 563862
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <div id="eventdump"></div>
+
+ <div id="hideable"><div id="hideable_child">Hi</div><div>there</div></div>
+
+ <div id="sortable" role="columnheader" aria-sort="none">aria-sort</div>
+
+ <div id="custom" role="custom" aria-blah="false">Fat free cheese</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_aria_owns.html b/accessible/tests/mochitest/events/test_aria_owns.html
new file mode 100644
index 000000000..a415a6d8d
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_aria_owns.html
@@ -0,0 +1,129 @@
+<html>
+
+<head>
+ <title>Aria-owns targets shouldn't be on invalidation list so shouldn't have
+ show/hide events</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Do tests.
+
+ //gA11yEventDumpToConsole = true; // debug stuff
+ //enableLogging("tree,eventTree,verbose");
+
+ /**
+ * Aria-owns target shouldn't have a show event.
+ * Markup:
+ * <div id="t1_fc" aria-owns="t1_owns"></div>
+ * <span id="t1_owns"></div>
+ */
+ function testAriaOwns()
+ {
+ this.parent = getNode("t1");
+ this.fc = document.createElement("div");
+ this.fc.setAttribute("id", "t1_fc");
+ this.owns = document.createElement("span");
+ this.owns.setAttribute("id", "t1_owns");
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, this.fc),
+ new unexpectedInvokerChecker(EVENT_SHOW, this.owns)
+ ];
+
+ this.invoke = function testAriaOwns_invoke()
+ {
+ getNode("t1").appendChild(this.fc);
+ getNode("t1").appendChild(this.owns);
+ getNode("t1_fc").setAttribute("aria-owns", "t1_owns");
+ };
+
+ this.getID = function testAriaOwns_getID() {
+ return "Aria-owns target shouldn't have show event";
+ };
+ }
+
+ /**
+ * Target of both aria-owns and other aria attribute like aria-labelledby
+ * shouldn't have a show event.
+ * Markup:
+ * <div id="t2_fc" aria-owns="t1_owns"></div>
+ * <div id="t2_sc" aria-labelledby="t2_owns"></div>
+ * <span id="t2_owns"></div>
+ */
+ function testAriaOwnsAndLabelledBy()
+ {
+ this.parent = getNode("t2");
+ this.fc = document.createElement("div");
+ this.fc.setAttribute("id", "t2_fc");
+ this.sc = document.createElement("div");
+ this.sc.setAttribute("id", "t2_sc");
+ this.owns = document.createElement("span");
+ this.owns.setAttribute("id", "t2_owns");
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, this.fc),
+ new invokerChecker(EVENT_SHOW, this.sc),
+ new unexpectedInvokerChecker(EVENT_SHOW, this.owns)
+ ];
+
+ this.invoke = function testAriaOwns_invoke()
+ {
+ getNode("t2").appendChild(this.fc);
+ getNode("t2").appendChild(this.sc);
+ getNode("t2").appendChild(this.owns);
+ getNode("t2_fc").setAttribute("aria-owns", "t2_owns");
+ getNode("t2_sc").setAttribute("aria-labelledby", "t2_owns");
+ };
+
+ this.getID = function testAriaOwns_getID() {
+ return "Aria-owns and aria-labelledby target shouldn't have show event";
+ };
+ }
+
+ var gQueue = null;
+ function doTests()
+ {
+ gQueue = new eventQueue();
+ gQueue.push(new testAriaOwns());
+ gQueue.push(new testAriaOwnsAndLabelledBy());
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1296420"
+ title="Aria-owns targets shouldn't be on invalidation list so shouldn't
+ have show/hide events">
+ Mozilla Bug 1296420
+ </a><br>
+
+ <div id="testContainer">
+ <div id="t1"></div>
+
+ <div id="t2"></div>
+ </div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_aria_statechange.html b/accessible/tests/mochitest/events/test_aria_statechange.html
new file mode 100644
index 000000000..d8c833157
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_aria_statechange.html
@@ -0,0 +1,208 @@
+<html>
+
+<head>
+ <title>ARIA state change event testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+
+ /**
+ * Do tests.
+ */
+ var gQueue = null;
+
+ //gA11yEventDumpID = "eventdump"; // debugging
+ //gA11yEventDumpToConsole = true; // debugging
+
+ function expandNode(aID, aIsExpanded)
+ {
+ this.DOMNode = getNode(aID);
+
+ this.eventSeq = [
+ new expandedStateChecker(aIsExpanded, this.DOMNode)
+ ];
+
+ this.invoke = function expandNode_invoke()
+ {
+ this.DOMNode.setAttribute("aria-expanded",
+ (aIsExpanded ? "true" : "false"));
+ };
+
+ this.getID = function expandNode_getID()
+ {
+ return prettyName(aID) + " aria-expanded changed to '" + aIsExpanded + "'";
+ };
+ }
+
+ function busyify(aID, aIsBusy)
+ {
+ this.DOMNode = getNode(aID);
+
+ this.eventSeq = [
+ new stateChangeChecker(STATE_BUSY, kOrdinalState, aIsBusy, this.DOMNode)
+ ];
+
+ this.invoke = function busyify_invoke()
+ {
+ this.DOMNode.setAttribute("aria-busy", (aIsBusy ? "true" : "false"));
+ };
+
+ this.getID = function busyify_getID()
+ {
+ return prettyName(aID) + " aria-busy changed to '" + aIsBusy + "'";
+ };
+ }
+
+ function setAttrOfMixedType(aID, aAttr, aState, aValue)
+ {
+ this.DOMNode = getNode(aID);
+
+ this.eventSeq = [
+ new stateChangeChecker(aState, kOrdinalState,
+ aValue == "true", this.DOMNode)
+ ];
+
+ if (hasState(aID, STATE_MIXED) || aValue == "mixed") {
+ this.eventSeq.push(
+ new stateChangeChecker(STATE_MIXED, kOrdinalState,
+ aValue == "mixed", this.DOMNode)
+ );
+ }
+
+ this.invoke = function setAttrOfMixedType_invoke()
+ {
+ this.DOMNode.setAttribute(aAttr, aValue);
+ };
+
+ this.getID = function setAttrOfMixedType_getID()
+ {
+ return prettyName(aID) + " " + aAttr + " changed to '" + aValue + "'";
+ };
+ }
+
+ function setPressed(aID, aValue)
+ {
+ this.__proto__ =
+ new setAttrOfMixedType(aID, "aria-pressed", STATE_PRESSED, aValue);
+ }
+
+ function setChecked(aID, aValue)
+ {
+ this.__proto__ =
+ new setAttrOfMixedType(aID, "aria-checked", STATE_CHECKED, aValue);
+ }
+
+ function buildQueueForAttr(aList, aQueue, aID, aInvokerFunc)
+ {
+ for (var i = 0; i < aList.length; i++) {
+ for (var j = i + 1; j < aList.length; j++) {
+ // XXX: changes from/to "undefined"/"" shouldn't fire state change
+ // events, bug 472142.
+ aQueue.push(new aInvokerFunc(aID, aList[i]));
+ aQueue.push(new aInvokerFunc(aID, aList[j]));
+ }
+ }
+ }
+
+ function buildQueueForAttrOfMixedType(aQueue, aID, aInvokerFunc)
+ {
+ var list = [ "", "undefined", "false", "true", "mixed" ];
+ buildQueueForAttr(list, aQueue, aID, aInvokerFunc);
+ }
+
+ function buildQueueForAttrOfBoolType(aQueue, aID, aInvokerFunc)
+ {
+ var list = [ "", "undefined", "false", "true" ];
+ buildQueueForAttr(list, aQueue, aID, aInvokerFunc);
+ }
+
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new expandNode("section", true));
+ gQueue.push(new expandNode("section", false));
+ gQueue.push(new expandNode("div", true));
+ gQueue.push(new expandNode("div", false));
+
+ gQueue.push(new busyify("aria_doc", true));
+ gQueue.push(new busyify("aria_doc", false));
+
+ buildQueueForAttrOfMixedType(gQueue, "pressable", setPressed);
+ buildQueueForAttrOfMixedType(gQueue, "pressable_native", setPressed);
+ buildQueueForAttrOfMixedType(gQueue, "checkable", setChecked);
+ buildQueueForAttrOfBoolType(gQueue, "checkableBool", setChecked);
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=551684"
+ title="No statechange event for aria-expanded on native HTML elements, is fired on ARIA widgets">
+ Mozilla Bug 551684
+ </a><br>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=648133"
+ title="fire state change event for aria-busy">
+ Mozilla Bug 648133
+ </a><br>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=467143"
+ title="mixed state change event is fired for focused accessible only">
+ Mozilla Bug 467143
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=989958"
+ title="Pressed state is not exposed on a button element with aria-pressed attribute">
+ Mozilla Bug 989958
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1136563"
+ title="Support ARIA 1.1 switch role">
+ Mozilla Bug 1136563
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <div id="eventdump"></div>
+
+ <!-- aria-expanded -->
+ <div id="section" role="section" aria-expanded="false">expandable section</div>
+ <div id="div" aria-expanded="false">expandable native div</div>
+
+ <!-- aria-busy -->
+ <div id="aria_doc" role="document" tabindex="0">A document</div>
+
+ <!-- aria-pressed -->
+ <div id="pressable" role="button"></div>
+ <button id="pressable_native"></button>
+
+ <!-- aria-checked -->
+ <div id="checkable" role="checkbox"></div>
+ <div id="checkableBool" role="switch"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_attrs.html b/accessible/tests/mochitest/events/test_attrs.html
new file mode 100644
index 000000000..1d5577572
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_attrs.html
@@ -0,0 +1,90 @@
+<html>
+
+<head>
+ <title>Event object attributes tests</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript">
+
+ /**
+ * Test "event-from-input" object attribute.
+ */
+ function eventFromInputChecker(aEventType, aID, aValue, aNoTargetID)
+ {
+ this.type = aEventType;
+ this.target = getAccessible(aID);
+
+ this.noTarget = getNode(aNoTargetID);
+
+ this.check = function checker_check(aEvent)
+ {
+ testAttrs(aEvent.accessible, { "event-from-input": aValue }, true);
+
+ var accessible = getAccessible(this.noTarget);
+ testAbsentAttrs(accessible, { "event-from-input": "" });
+ }
+ }
+
+ /**
+ * Do tests.
+ */
+ var gQueue = null;
+
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ var id = "textbox", noTargetId = "textarea";
+ var checker =
+ new eventFromInputChecker(EVENT_FOCUS, id, "false", noTargetId);
+ gQueue.push(new synthFocus(id, checker));
+
+ if (!MAC) { // Mac failure is bug 541093
+ var checker =
+ new eventFromInputChecker(EVENT_TEXT_CARET_MOVED, id, "true", noTargetId);
+ gQueue.push(new synthHomeKey(id, checker));
+ }
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=540285"
+ title="Event object attributes testing">
+ Mozilla Bug 540285
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="textbox" value="hello">
+ <textarea id="textarea"></textarea>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_bug1322593-2.html b/accessible/tests/mochitest/events/test_bug1322593-2.html
new file mode 100644
index 000000000..20136d393
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_bug1322593-2.html
@@ -0,0 +1,83 @@
+<html>
+
+<head>
+ <title>Accessible mutation events testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function changeMultipleElements()
+ {
+ this.node1 = getNode("span1");
+ this.node2 = getNode("span2");
+
+ this.eventSeq = [
+ new textChangeChecker("container", 0, 5, "hello", false, undefined, true),
+ new textChangeChecker("container", 6, 11, "world", false, undefined, true),
+ new orderChecker(),
+ new textChangeChecker("container", 0, 1, "a", true, undefined, true),
+ new textChangeChecker("container", 7, 8, "b", true, undefined, true)
+ ];
+
+ this.invoke = function changeMultipleElements_invoke()
+ {
+ this.node1.textContent = "a";
+ this.node2.textContent = "b";
+ }
+
+ this.getID = function changeMultipleElements_invoke_getID()
+ {
+ return "Change the text content of multiple sibling divs";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Do tests
+// gA11yEventDumpToConsole = true; // debugging
+
+ var gQueue = null;
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new changeMultipleElements());
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1322593"
+ title="missing text change events when multiple elements updated at once">
+ Mozilla Bug 1322593
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="container">
+ <span id="span1">hello</span>
+ <span>your</span>
+ <span id="span2">world</span>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_bug1322593.html b/accessible/tests/mochitest/events/test_bug1322593.html
new file mode 100644
index 000000000..38786d0b9
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_bug1322593.html
@@ -0,0 +1,80 @@
+<html>
+
+<head>
+ <title>Accessible mutation events testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function changeMultipleElements()
+ {
+ this.node1 = getNode("div1");
+ this.node2 = getNode("div2");
+
+ this.eventSeq = [
+ new textChangeChecker("div1", 0, 5, "hello", false, undefined, true),
+ new textChangeChecker("div2", 0, 5, "world", false, undefined, true),
+ new orderChecker(),
+ new textChangeChecker("div1", 0, 1, "a", true, undefined, true),
+ new textChangeChecker("div2", 0, 1, "b", true, undefined, true)
+ ];
+
+ this.invoke = function changeMultipleElements_invoke()
+ {
+ this.node1.textContent = "a";
+ this.node2.textContent = "b";
+ }
+
+ this.getID = function changeMultipleElements_invoke_getID()
+ {
+ return "Change the text content of multiple sibling divs";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Do tests
+// gA11yEventDumpToConsole = true; // debugging
+
+ var gQueue = null;
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new changeMultipleElements());
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1322593"
+ title="missing text change events when multiple elements updated at once">
+ Mozilla Bug 1322593
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="div1">hello</div>
+ <div id="div2">world</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_caretmove.html b/accessible/tests/mochitest/events/test_caretmove.html
new file mode 100644
index 000000000..6d39c4ef6
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_caretmove.html
@@ -0,0 +1,140 @@
+<html>
+
+<head>
+ <title>Accessible caret move events testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ /**
+ * Click checker.
+ */
+ function clickChecker(aCaretOffset, aID, aExtraNodeOrID, aExtraCaretOffset)
+ {
+ this.__proto__ = new caretMoveChecker(aCaretOffset, aID);
+
+ this.extraNode = getNode(aExtraNodeOrID);
+
+ this.check = function clickChecker_check(aEvent)
+ {
+ this.__proto__.check(aEvent);
+
+ if (this.extraNode) {
+ var acc = getAccessible(this.extraNode, [nsIAccessibleText]);
+ is(acc.caretOffset, aExtraCaretOffset,
+ "Wrong caret offset for " + aExtraNodeOrID);
+ }
+ }
+ }
+
+ /**
+ * Do tests.
+ */
+ var gQueue = null;
+
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ function doTests()
+ {
+ // test caret move events and caret offsets
+ gQueue = new eventQueue();
+
+ var id = "textbox";
+ gQueue.push(new synthFocus(id, new caretMoveChecker(5, id)));
+ gQueue.push(new synthSelectAll(id, new caretMoveChecker(5, id)));
+ gQueue.push(new synthClick(id, new caretMoveChecker(0, id)));
+ gQueue.push(new synthRightKey(id, new caretMoveChecker(1, id)));
+
+ id = "textarea";
+ gQueue.push(new synthClick(id, new caretMoveChecker(0, id)));
+ gQueue.push(new synthRightKey(id, new caretMoveChecker(1, id)));
+ gQueue.push(new synthDownKey(id, new caretMoveChecker(12, id)));
+
+ id = "textarea_wrapped";
+ gQueue.push(new setCaretOffset(id, 4, id));
+ gQueue.push(new synthLeftKey(id, new caretMoveChecker(4, id)));
+
+ id = "p";
+ gQueue.push(new synthClick(id, new caretMoveChecker(0, id)));
+ gQueue.push(new synthRightKey(id, new caretMoveChecker(1, id)));
+ gQueue.push(new synthDownKey(id, new caretMoveChecker(6, id)));
+
+ id = "p1_in_div";
+ gQueue.push(new synthClick(id, new clickChecker(0, id, "p2_in_div", -1)));
+
+ id = "p";
+ gQueue.push(new synthShiftTab(id, new caretMoveChecker(0, id)));
+ id = "textarea";
+ gQueue.push(new synthShiftTab(id, new caretMoveChecker(12, id)));
+ id = "p";
+ gQueue.push(new synthTab(id, new caretMoveChecker(0, id)));
+
+ // Set caret after a child of span element, i.e. after 'text' text.
+ gQueue.push(new moveCaretToDOMPoint("test1", getNode("test1_span"), 1,
+ 4, "test1"));
+ gQueue.push(new moveCaretToDOMPoint("test2", getNode("test2_span"), 1,
+ 4, "test2"));
+
+ // empty text element
+ gQueue.push(new moveCaretToDOMPoint("test3", getNode("test3"), 0,
+ 0, "test3"));
+ gQueue.push(new moveCaretToDOMPoint("test4", getNode("test4_span"), 0,
+ 0, "test4"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=454377"
+ title="Accessible caret move events testing">
+ Bug 454377
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=567571"
+ title="caret-moved events missing at the end of a wrapped line of text">
+ Bug 567571
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=824901"
+ title="HyperTextAccessible::DOMPointToHypertextOffset fails for node and offset equal to node child count">
+ Bug 824901
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="textbox" value="hello"/>
+ <textarea id="textarea">text<br>text</textarea>
+ <p id="p" contentEditable="true"><span>text</span><br/>text</p>
+ <div id="div" contentEditable="true"><p id="p1_in_div">text</p><p id="p2_in_div">text</p></div>
+
+ <p contentEditable="true" id="test1"><span id="test1_span">text</span>ohoho</p>
+ <p contentEditable="true" id="test2"><span><span id="test2_span">text</span></span>ohoho</p>
+ <p contentEditable="true" id="test3"></p>
+ <p contentEditable="true" id="test4"><span id="test4_span"></span></p>
+
+ <textarea id="textarea_wrapped" cols="5">hey friend</textarea>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_caretmove.xul b/accessible/tests/mochitest/events/test_caretmove.xul
new file mode 100644
index 000000000..cf4dcd483
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_caretmove.xul
@@ -0,0 +1,72 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Caret move event testing">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ /**
+ * Do tests.
+ */
+
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTests()
+ {
+ if (MAC) {
+ todo(false, "Make these tests pass on OSX (bug 650294)");
+ SimpleTest.finish();
+ return;
+ }
+
+ gQueue = new eventQueue(EVENT_TEXT_CARET_MOVED);
+
+ var id = "textbox";
+ var input = getNode(id).inputField;
+ gQueue.push(new synthFocus(id, new caretMoveChecker(5, input)));
+ gQueue.push(new synthSelectAll(id, new caretMoveChecker(5, input)));
+ gQueue.push(new synthHomeKey(id, new caretMoveChecker(0, input)));
+ gQueue.push(new synthRightKey(id, new caretMoveChecker(1, input)));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=634240"
+ title="No caret move events are fired for XUL textbox accessible">
+ Mozilla Bug 634240
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <textbox id="textbox" value="hello"/>
+
+ <vbox id="eventdump"/>
+ </vbox>
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_coalescence.html b/accessible/tests/mochitest/events/test_coalescence.html
new file mode 100644
index 000000000..d95ef99b0
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_coalescence.html
@@ -0,0 +1,864 @@
+<html>
+
+<head>
+ <title>Accessible mutation events coalescence testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invoker base classes
+
+ const kRemoveElm = 1;
+ const kHideElm = 2;
+ const kAddElm = 3;
+ const kShowElm = 4;
+
+ /**
+ * Base class to test of mutation events coalescence.
+ */
+ function coalescenceBase(aChildAction, aParentAction,
+ aPerformActionOnChildInTheFirstPlace)
+ {
+ // Invoker interface
+
+ this.invoke = function coalescenceBase_invoke()
+ {
+ if (aPerformActionOnChildInTheFirstPlace) {
+ this.invokeAction(this.childNode, aChildAction);
+ this.invokeAction(this.parentNode, aParentAction);
+ } else {
+ this.invokeAction(this.parentNode, aParentAction);
+ this.invokeAction(this.childNode, aChildAction);
+ }
+ }
+
+ this.getID = function coalescenceBase_getID()
+ {
+ var childAction = this.getActionName(aChildAction) + " child";
+ var parentAction = this.getActionName(aParentAction) + " parent";
+
+ if (aPerformActionOnChildInTheFirstPlace)
+ return childAction + " and then " + parentAction;
+
+ return parentAction + " and then " + childAction;
+ }
+
+ this.finalCheck = function coalescenceBase_check()
+ {
+ if (this.getEventType(aChildAction) == EVENT_HIDE) {
+ testIsDefunct(this.child);
+ }
+ if (this.getEventType(aParentAction) == EVENT_HIDE) {
+ testIsDefunct(this.parent);
+ }
+ }
+
+ // Implementation details
+
+ this.invokeAction = function coalescenceBase_invokeAction(aNode, aAction)
+ {
+ switch (aAction) {
+ case kRemoveElm:
+ aNode.parentNode.removeChild(aNode);
+ break;
+
+ case kHideElm:
+ aNode.style.display = "none";
+ break;
+
+ case kAddElm:
+ if (aNode == this.parentNode)
+ this.hostNode.appendChild(this.parentNode);
+ else
+ this.parentNode.appendChild(this.childNode);
+ break;
+
+ case kShowElm:
+ aNode.style.display = "block";
+ break;
+
+ default:
+ return INVOKER_ACTION_FAILED;
+ }
+ }
+
+ this.getEventType = function coalescenceBase_getEventType(aAction)
+ {
+ switch (aAction) {
+ case kRemoveElm: case kHideElm:
+ return EVENT_HIDE;
+ case kAddElm: case kShowElm:
+ return EVENT_SHOW;
+ }
+ }
+
+ this.getActionName = function coalescenceBase_getActionName(aAction)
+ {
+ switch (aAction) {
+ case kRemoveElm:
+ return "remove";
+ case kHideElm:
+ return "hide";
+ case kAddElm:
+ return "add";
+ case kShowElm:
+ return "show";
+ default:
+ return "??";
+ }
+ }
+
+ this.initSequence = function coalescenceBase_initSequence()
+ {
+ // expected events
+ var eventType = this.getEventType(aParentAction);
+ this.eventSeq = [
+ new invokerChecker(eventType, this.parentNode),
+ new invokerChecker(EVENT_REORDER, this.hostNode)
+ ];
+
+ // unexpected events
+ this.unexpectedEventSeq = [
+ new invokerChecker(this.getEventType(aChildAction), this.childNode),
+ new invokerChecker(EVENT_REORDER, this.parentNode)
+ ];
+ }
+ }
+
+ /**
+ * Remove or hide mutation events coalescence testing.
+ */
+ function removeOrHideCoalescenceBase(aChildID, aParentID,
+ aChildAction, aParentAction,
+ aPerformActionOnChildInTheFirstPlace)
+ {
+ this.__proto__ = new coalescenceBase(aChildAction, aParentAction,
+ aPerformActionOnChildInTheFirstPlace);
+
+ this.init = function removeOrHideCoalescenceBase_init()
+ {
+ this.childNode = getNode(aChildID);
+ this.parentNode = getNode(aParentID);
+ this.child = getAccessible(this.childNode);
+ this.parent = getAccessible(this.parentNode);
+ this.hostNode = this.parentNode.parentNode;
+ }
+
+ // Initalization
+
+ this.init();
+ this.initSequence();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ /**
+ * Remove child node and then its parent node from DOM tree.
+ */
+ function removeChildNParent(aChildID, aParentID)
+ {
+ this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID,
+ kRemoveElm, kRemoveElm,
+ true);
+ }
+
+ /**
+ * Remove parent node and then its child node from DOM tree.
+ */
+ function removeParentNChild(aChildID, aParentID)
+ {
+ this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID,
+ kRemoveElm, kRemoveElm,
+ false);
+ }
+
+ /**
+ * Hide child node and then its parent node.
+ */
+ function hideChildNParent(aChildID, aParentID)
+ {
+ this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID,
+ kHideElm, kHideElm,
+ true);
+ }
+
+ /**
+ * Hide parent node and then its child node.
+ */
+ function hideParentNChild(aChildID, aParentID)
+ {
+ this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID,
+ kHideElm, kHideElm,
+ false);
+ }
+
+ /**
+ * Hide child node and then remove its parent node.
+ */
+ function hideChildNRemoveParent(aChildID, aParentID)
+ {
+ this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID,
+ kHideElm, kRemoveElm,
+ true);
+ }
+
+ /**
+ * Hide parent node and then remove its child node.
+ */
+ function hideParentNRemoveChild(aChildID, aParentID)
+ {
+ this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID,
+ kRemoveElm, kHideElm,
+ false);
+ }
+
+ /**
+ * Remove child node and then hide its parent node.
+ */
+ function removeChildNHideParent(aChildID, aParentID)
+ {
+ this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID,
+ kRemoveElm, kHideElm,
+ true);
+ }
+
+ /**
+ * Remove parent node and then hide its child node.
+ */
+ function removeParentNHideChild(aChildID, aParentID)
+ {
+ this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID,
+ kHideElm, kRemoveElm,
+ false);
+ }
+
+ /**
+ * Create and append parent node and create and append child node to it.
+ */
+ function addParentNChild(aHostID, aPerformActionOnChildInTheFirstPlace)
+ {
+ this.init = function addParentNChild_init()
+ {
+ this.hostNode = getNode(aHostID);
+ this.parentNode = document.createElement("select");
+ this.childNode = document.createElement("option");
+ this.childNode.textContent = "testing";
+ }
+
+ this.__proto__ = new coalescenceBase(kAddElm, kAddElm,
+ aPerformActionOnChildInTheFirstPlace);
+
+ this.init();
+ this.initSequence();
+ }
+
+ /**
+ * Show parent node and show child node to it.
+ */
+ function showParentNChild(aParentID, aChildID,
+ aPerformActionOnChildInTheFirstPlace)
+ {
+ this.init = function showParentNChild_init()
+ {
+ this.parentNode = getNode(aParentID);
+ this.hostNode = this.parentNode.parentNode;
+ this.childNode = getNode(aChildID);
+ }
+
+ this.__proto__ = new coalescenceBase(kShowElm, kShowElm,
+ aPerformActionOnChildInTheFirstPlace);
+
+ this.init();
+ this.initSequence();
+ }
+
+ /**
+ * Create and append child node to the DOM and then show parent node.
+ */
+ function showParentNAddChild(aParentID,
+ aPerformActionOnChildInTheFirstPlace)
+ {
+ this.init = function showParentNAddChild_init()
+ {
+ this.parentNode = getNode(aParentID);
+ this.hostNode = this.parentNode.parentNode;
+ this.childNode = document.createElement("option");
+ this.childNode.textContent = "testing";
+ }
+
+ this.__proto__ = new coalescenceBase(kAddElm, kShowElm,
+ aPerformActionOnChildInTheFirstPlace);
+
+ this.init();
+ this.initSequence();
+ }
+
+ /**
+ * Remove children and parent
+ */
+ function removeGrandChildrenNHideParent(aChild1Id, aChild2Id, aParentId)
+ {
+ this.child1 = getNode(aChild1Id);
+ this.child2 = getNode(aChild2Id);
+ this.parent = getNode(aParentId);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getAccessible(aParentId)),
+ new invokerChecker(EVENT_REORDER, getNode(aParentId).parentNode),
+ new unexpectedInvokerChecker(EVENT_HIDE, getAccessible(aChild1Id)),
+ new unexpectedInvokerChecker(EVENT_HIDE, getAccessible(aChild2Id)),
+ new unexpectedInvokerChecker(EVENT_REORDER, getAccessible(aParentId))
+ ];
+
+ this.invoke = function removeGrandChildrenNHideParent_invoke()
+ {
+ this.child1.parentNode.removeChild(this.child1);
+ this.child2.parentNode.removeChild(this.child2);
+ this.parent.hidden = true;
+ }
+
+ this.getID = function removeGrandChildrenNHideParent_getID() {
+ return "remove grand children of different parents and then hide their grand parent";
+ }
+ }
+
+ /**
+ * Remove a child, and then its parent.
+ */
+ function test3()
+ {
+ this.o = getAccessible("t3_o");
+ this.ofc = getAccessible("t3_o").firstChild;
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, this.o),
+ new invokerChecker(EVENT_REORDER, "t3_lb"),
+ new unexpectedInvokerChecker(EVENT_HIDE, this.ofc),
+ new unexpectedInvokerChecker(EVENT_REORDER, this.o)
+ ];
+
+ this.invoke = function test3_invoke()
+ {
+ getNode("t3_o").textContent = "";
+ getNode("t3_lb").removeChild(getNode("t3_o"));
+ }
+
+ this.finalCheck = function test3_finalCheck()
+ {
+ testIsDefunct(this.o);
+ testIsDefunct(this.ofc);
+ }
+
+ this.getID = function test3_getID() {
+ return "remove a child, and then its parent";
+ }
+ }
+
+ /**
+ * Remove children, and then a parent of 2nd child.
+ */
+ function test4()
+ {
+ this.o1 = getAccessible("t4_o1");
+ this.o1fc = this.o1.firstChild;
+ this.o2 = getAccessible("t4_o2");
+ this.o2fc = this.o2.firstChild;
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, this.o1fc),
+ new invokerChecker(EVENT_HIDE, this.o2),
+ new invokerChecker(EVENT_REORDER, "t4_lb"),
+ new unexpectedInvokerChecker(EVENT_HIDE, this.o2fc),
+ new unexpectedInvokerChecker(EVENT_REORDER, this.o1),
+ new unexpectedInvokerChecker(EVENT_REORDER, this.o2)
+ ];
+
+ this.invoke = function test4_invoke()
+ {
+ getNode("t4_o1").textContent = "";
+ getNode("t4_o2").textContent = "";
+ getNode("t4_lb").removeChild(getNode("t4_o2"));
+ }
+
+ this.finalCheck = function test4_finalCheck()
+ {
+ testIsDefunct(this.o1fc);
+ testIsDefunct(this.o2);
+ testIsDefunct(this.o2fc);
+ }
+
+ this.getID = function test4_getID() {
+ return "remove children, and then a parent of 2nd child";
+ }
+ }
+
+ /**
+ * Remove a child, remove a parent sibling, remove the parent
+ */
+ function test5()
+ {
+ this.o = getAccessible("t5_o");
+ this.ofc = this.o.firstChild;
+ this.b = getAccessible("t5_b");
+ this.lb = getAccessible("t5_lb");
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, this.b),
+ new invokerChecker(EVENT_HIDE, this.o),
+ new invokerChecker(EVENT_REORDER, "t5"),
+ new unexpectedInvokerChecker(EVENT_HIDE, this.ofc),
+ new unexpectedInvokerChecker(EVENT_REORDER, this.o),
+ new unexpectedInvokerChecker(EVENT_REORDER, this.lb)
+ ];
+
+ this.invoke = function test5_invoke()
+ {
+ getNode("t5_o").textContent = "";
+ getNode("t5").removeChild(getNode("t5_b"));
+ getNode("t5_lb").removeChild(getNode("t5_o"));
+ }
+
+ this.finalCheck = function test5_finalCheck()
+ {
+ testIsDefunct(this.ofc);
+ testIsDefunct(this.o);
+ testIsDefunct(this.b);
+ }
+
+ this.getID = function test5_getID() {
+ return "remove a child, remove a parent sibling, remove the parent";
+ }
+ }
+
+ /**
+ * Insert accessibles with a child node moved by aria-owns
+ * Markup:
+ * <div id="t6_fc">
+ * <div id="t6_owns"></div>
+ * </div>
+ * <div id="t6_sc" aria-owns="t6_owns"></div>
+ */
+ function test6()
+ {
+ this.parent = getNode("t6");
+ this.fc = document.createElement("div");
+ this.fc.setAttribute("id", "t6_fc");
+ this.owns = document.createElement("div");
+ this.owns.setAttribute("id", "t6_owns");
+ this.sc = document.createElement("div");
+ this.sc.setAttribute("id", "t6_sc");
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, this.fc),
+ new invokerChecker(EVENT_SHOW, this.sc),
+ new invokerChecker(EVENT_REORDER, this.parent),
+ new unexpectedInvokerChecker(EVENT_REORDER, this.fc),
+ new unexpectedInvokerChecker(EVENT_REORDER, this.sc),
+ new unexpectedInvokerChecker(EVENT_HIDE, this.owns),
+ new unexpectedInvokerChecker(EVENT_SHOW, this.owns)
+ ];
+
+ this.invoke = function test6_invoke()
+ {
+ getNode("t6").appendChild(this.fc);
+ getNode("t6_fc").appendChild(this.owns);
+ getNode("t6").appendChild(this.sc);
+ getNode("t6_sc").setAttribute("aria-owns", "t6_owns");
+ };
+
+ this.getID = function test6_getID() {
+ return "Insert accessibles with a child node moved by aria-owns";
+ };
+ }
+
+ /**
+ * Insert text nodes under direct and grand children, and then hide
+ * their container by means of aria-owns.
+ *
+ * Markup:
+ * <div id="t7_moveplace" aria-owns="t7_c"></div>
+ * <div id="t7_c">
+ * <div id="t7_c_directchild">ha</div>
+ * <div><div id="t7_c_grandchild">ha</div></div>
+ * </div>
+ */
+ function test7()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode('t7_c')),
+ new invokerChecker(EVENT_SHOW, getNode('t7_c')),
+ new invokerChecker(EVENT_REORDER, getNode('t7')),
+ new unexpectedInvokerChecker(EVENT_REORDER, getNode('t7_c_directchild')),
+ new unexpectedInvokerChecker(EVENT_REORDER, getNode('t7_c_grandchild')),
+ new unexpectedInvokerChecker(EVENT_SHOW, () => getNode('t7_c_directchild').firstChild),
+ new unexpectedInvokerChecker(EVENT_SHOW, () => getNode('t7_c_grandchild').firstChild)
+ ];
+
+ this.invoke = function test7_invoke()
+ {
+ getNode('t7_c_directchild').textContent = 'ha';
+ getNode('t7_c_grandchild').textContent = 'ha';
+ getNode('t7_moveplace').setAttribute('aria-owns', 't7_c');
+ };
+
+ this.getID = function test7_getID() {
+ return "Show child accessibles and then hide their container";
+ };
+ }
+
+ /**
+ * Move a node by aria-owns from right to left in the tree, so that
+ * the eventing looks this way:
+ * reorder for 't8_c1'
+ * hide for 't8_c1_child'
+ * show for 't8_c2_moved'
+ * reorder for 't8_c2'
+ * hide for 't8_c2_moved'
+ *
+ * The hide event should be delivered before the paired show event.
+ */
+ function test8()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode('t8_c1_child')),
+ new invokerChecker(EVENT_HIDE, 't8_c2_moved'),
+ new invokerChecker(EVENT_SHOW, 't8_c2_moved'),
+ new invokerChecker(EVENT_REORDER, 't8_c2'),
+ new invokerChecker(EVENT_REORDER, 't8_c1'),
+ ];
+
+ this.invoke = function test8_invoke()
+ {
+ // Remove a node from 't8_c1' container to give the event tree a
+ // desired structure (the 't8_c1' container node goes first in the event
+ // tree)
+ getNode('t8_c1_child').remove();
+ // then move 't8_c2_moved' from 't8_c2' to 't8_c1'.
+ getNode('t8_c1').setAttribute('aria-owns', 't8_c2_moved');
+ };
+
+ this.getID = function test8_getID() {
+ return "Move a node by aria-owns to left within the tree";
+ };
+ }
+
+ /**
+ * Move 't9_c3_moved' node under 't9_c2_moved', and then move 't9_c2_moved'
+ * node by aria-owns (same as test10 but has different aria-owns
+ * ordering), the eventing looks same way as in test10:
+ * reorder for 't9_c1'
+ * hide for 't9_c1_child'
+ * show for 't9_c2_moved'
+ * reorder for 't9_c2'
+ * hide for 't9_c2_child'
+ * hide for 't9_c2_moved'
+ * reorder for 't9_c3'
+ * hide for 't9_c3_moved'
+ *
+ * The hide events for 't9_c2_moved' and 't9_c3_moved' should be delivered
+ * before the show event for 't9_c2_moved'.
+ */
+ function test9()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode('t9_c1_child')),
+ new invokerChecker(EVENT_HIDE, getNode('t9_c2_child')),
+ new invokerChecker(EVENT_HIDE, 't9_c3_moved'),
+ new invokerChecker(EVENT_HIDE, 't9_c2_moved'),
+ new invokerChecker(EVENT_SHOW, 't9_c2_moved'),
+ new invokerChecker(EVENT_REORDER, 't9_c3'),
+ new invokerChecker(EVENT_REORDER, 't9_c2'),
+ new invokerChecker(EVENT_REORDER, 't9_c1'),
+ new unexpectedInvokerChecker(EVENT_SHOW, 't9_c3_moved')
+ ];
+
+ this.invoke = function test9_invoke()
+ {
+ // Remove child nodes from 't9_c1' and 't9_c2' containers to give
+ // the event tree a needed structure ('t9_c1' and 't9_c2' nodes go
+ // first in the event tree),
+ getNode('t9_c1_child').remove();
+ getNode('t9_c2_child').remove();
+ // then do aria-owns magic.
+ getNode('t9_c2_moved').setAttribute('aria-owns', 't9_c3_moved');
+ getNode('t9_c1').setAttribute('aria-owns', 't9_c2_moved');
+ };
+
+ this.getID = function test9_getID() {
+ return "Move node #1 by aria-owns and then move node #2 into node #1";
+ };
+ }
+
+ /**
+ * Move a node 't10_c3_moved' by aria-owns under a node 't10_c2_moved',
+ * moved by under 't10_1', so that the eventing looks this way:
+ * reorder for 't10_c1'
+ * hide for 't10_c1_child'
+ * show for 't10_c2_moved'
+ * reorder for 't10_c2'
+ * hide for 't10_c2_child'
+ * hide for 't10_c2_moved'
+ * reorder for 't10_c3'
+ * hide for 't10_c3_moved'
+ *
+ * The hide events for 't10_c2_moved' and 't10_c3_moved' should be delivered
+ * before the show event for 't10_c2_moved'.
+ */
+ function test10()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode('t10_c1_child')),
+ new invokerChecker(EVENT_HIDE, getNode('t10_c2_child')),
+ new invokerChecker(EVENT_HIDE, getNode('t10_c2_moved')),
+ new invokerChecker(EVENT_HIDE, getNode('t10_c3_moved')),
+ new invokerChecker(EVENT_SHOW, getNode('t10_c2_moved')),
+ new invokerChecker(EVENT_REORDER, 't10_c2'),
+ new invokerChecker(EVENT_REORDER, 't10_c1'),
+ new invokerChecker(EVENT_REORDER, 't10_c3')
+ ];
+
+ this.invoke = function test10_invoke()
+ {
+ // Remove child nodes from 't10_c1' and 't10_c2' containers to give
+ // the event tree a needed structure ('t10_c1' and 't10_c2' nodes go first
+ // in the event tree),
+ getNode('t10_c1_child').remove();
+ getNode('t10_c2_child').remove();
+ // then do aria-owns stuff.
+ getNode('t10_c1').setAttribute('aria-owns', 't10_c2_moved');
+ getNode('t10_c2_moved').setAttribute('aria-owns', 't10_c3_moved');
+ };
+
+ this.getID = function test10_getID() {
+ return "Move a node by aria-owns into a node moved by aria-owns to left within the tree";
+ };
+ }
+
+ /**
+ * Move a node by aria-owns from right to left in the tree, and then
+ * move its parent too by aria-owns. No hide event should be fired for
+ * original node.
+ */
+ function test11()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode('t11_c1_child')),
+ new invokerChecker(EVENT_HIDE, getNode('t11_c2')),
+ new orderChecker(),
+ new asyncInvokerChecker(EVENT_SHOW, 't11_c2_child'),
+ new asyncInvokerChecker(EVENT_SHOW, 't11_c2'),
+ new orderChecker(),
+ new invokerChecker(EVENT_REORDER, 't11'),
+ new unexpectedInvokerChecker(EVENT_HIDE, 't11_c2_child'),
+ new unexpectedInvokerChecker(EVENT_REORDER, 't11_c1'),
+ new unexpectedInvokerChecker(EVENT_REORDER, 't11_c2'),
+ new unexpectedInvokerChecker(EVENT_REORDER, 't11_c3')
+ ];
+
+ this.invoke = function test11_invoke()
+ {
+ // Remove a node from 't11_c1' container to give the event tree a
+ // desired structure (the 't11_c1' container node goes first in
+ // the event tree),
+ getNode('t11_c1_child').remove();
+ // then move 't11_c2_moved' from 't11_c2' to 't11_c1', and then move
+ // 't11_c2' to 't11_c3'.
+ getNode('t11_c1').setAttribute('aria-owns', 't11_c2_child');
+ getNode('t11_c3').setAttribute('aria-owns', 't11_c2');
+ };
+
+ this.getID = function test11_getID() {
+ return "Move a node by aria-owns to left within the tree";
+ };
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Do tests.
+
+ gA11yEventDumpToConsole = true; // debug stuff
+ //enableLogging("eventTree");
+
+ var gQueue = null;
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new removeChildNParent("option1", "select1"));
+ gQueue.push(new removeParentNChild("option2", "select2"));
+ gQueue.push(new hideChildNParent("option3", "select3"));
+ gQueue.push(new hideParentNChild("option4", "select4"));
+ gQueue.push(new hideChildNRemoveParent("option5", "select5"));
+ gQueue.push(new hideParentNRemoveChild("option6", "select6"));
+ gQueue.push(new removeChildNHideParent("option7", "select7"));
+ gQueue.push(new removeParentNHideChild("option8", "select8"));
+
+ gQueue.push(new addParentNChild("testContainer", false));
+ gQueue.push(new addParentNChild("testContainer", true));
+ gQueue.push(new showParentNChild("select9", "option9", false));
+ gQueue.push(new showParentNChild("select10", "option10", true));
+ gQueue.push(new showParentNAddChild("select11", false));
+ gQueue.push(new showParentNAddChild("select12", true));
+
+ gQueue.push(new removeGrandChildrenNHideParent("t1_child1", "t1_child2", "t1_parent"));
+ gQueue.push(new test3());
+ gQueue.push(new test4());
+ gQueue.push(new test5());
+ gQueue.push(new test6());
+ gQueue.push(new test7());
+ gQueue.push(new test8());
+ gQueue.push(new test9());
+ gQueue.push(new test10());
+ gQueue.push(new test11());
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=513213"
+ title="coalesce events when new event is appended to the queue">
+ Mozilla Bug 513213
+ </a><br>
+ <a target="_blank"
+ title="Rework accessible tree update code"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275">
+ Mozilla Bug 570275
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="testContainer">
+ <select id="select1">
+ <option id="option1">option</option>
+ </select>
+ <select id="select2">
+ <option id="option2">option</option>
+ </select>
+ <select id="select3">
+ <option id="option3">option</option>
+ </select>
+ <select id="select4">
+ <option id="option4">option</option>
+ </select>
+ <select id="select5">
+ <option id="option5">option</option>
+ </select>
+ <select id="select6">
+ <option id="option6">option</option>
+ </select>
+ <select id="select7">
+ <option id="option7">option</option>
+ </select>
+ <select id="select8">
+ <option id="option8">option</option>
+ </select>
+
+ <select id="select9" style="display: none">
+ <option id="option9" style="display: none">testing</option>
+ </select>
+ <select id="select10" style="display: none">
+ <option id="option10" style="display: none">testing</option>
+ </select>
+ <select id="select11" style="display: none"></select>
+ <select id="select12" style="display: none"></select>
+ </div>
+
+ <div id="testContainer2">
+ <div id="t1_parent">
+ <div id="t1_mid1"><div id="t1_child1"></div></div>
+ <div id="t1_mid2"><div id="t1_child2"></div></div>
+ </div>
+ </div>
+
+ <div id="t3">
+ <div role="listbox" id="t3_lb">
+ <div role="option" id="t3_o">opt</div>
+ </div>
+ </div>
+
+ <div id="t4">
+ <div role="listbox" id="t4_lb">
+ <div role="option" id="t4_o1">opt1</div>
+ <div role="option" id="t4_o2">opt2</div>
+ </div>
+ </div>
+
+ <div id="t5">
+ <div role="button" id="t5_b">btn</div>
+ <div role="listbox" id="t5_lb">
+ <div role="option" id="t5_o">opt</div>
+ </div>
+ </div>
+
+ <div id="t6">
+ </div>
+
+ <div id="t7">
+ <div id="t7_moveplace"></div>
+ <div id="t7_c">
+ <div><div id="t7_c_grandchild"></div></div>
+ <div id="t7_c_directchild"></div>
+ </div>
+ </div>
+
+ <div id="t8">
+ <div id="t8_c1"><div id="t8_c1_child"></div></div>
+ <div id="t8_c2">
+ <div id="t8_c2_moved"></div>
+ </div>
+ </div>
+
+ <div id="t9">
+ <div id="t9_c1"><div id="t9_c1_child"></div></div>
+ <div id="t9_c2">
+ <div id="t9_c2_child"></div>
+ <div id="t9_c2_moved"></div>
+ </div>
+ <div id="t9_c3">
+ <div id="t9_c3_moved"></div>
+ </div>
+ </div>
+
+ <div id="t10">
+ <div id="t10_c1"><div id="t10_c1_child"></div></div>
+ <div id="t10_c2">
+ <div id="t10_c2_child"></div>
+ <div id="t10_c2_moved"></div>
+ </div>
+ <div id="t10_c3">
+ <div id="t10_c3_moved"></div>
+ </div>
+ </div>
+
+ <div id="t11">
+ <div id="t11_c1"><div id="t11_c1_child"></div></div>
+ <div id="t11_c2"><div id="t11_c2_child"></div></div>
+ <div id="t11_c3"></div>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_contextmenu.html b/accessible/tests/mochitest/events/test_contextmenu.html
new file mode 100644
index 000000000..065e1b50e
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_contextmenu.html
@@ -0,0 +1,139 @@
+<html>
+
+<head>
+ <title>Context menu tests</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function showContextMenu(aID)
+ {
+ this.DOMNode = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_MENUPOPUP_START, getContextMenuNode()),
+ ];
+
+ this.invoke = function showContextMenu_invoke()
+ {
+ synthesizeMouse(this.DOMNode, 4, 4, { type: "contextmenu", button: 2 });
+ }
+
+ this.getID = function showContextMenu_getID()
+ {
+ return "show context menu";
+ }
+ }
+
+ function selectMenuItem()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, getFocusedMenuItem)
+ ];
+
+ this.invoke = function selectMenuItem_invoke()
+ {
+ synthesizeKey("VK_DOWN", { });
+ }
+
+ this.getID = function selectMenuItem_getID()
+ {
+ return "select first menuitem";
+ }
+ }
+
+ function closeContextMenu(aID)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_MENUPOPUP_END,
+ getAccessible(getContextMenuNode()))
+ ];
+
+ this.invoke = function closeContextMenu_invoke()
+ {
+ synthesizeKey("VK_ESCAPE", { });
+ }
+
+ this.getID = function closeContextMenu_getID()
+ {
+ return "close context menu";
+ }
+ }
+
+ function getContextMenuNode()
+ {
+ return getRootAccessible().DOMDocument.
+ getElementById("contentAreaContextMenu");
+ }
+
+ function getFocusedMenuItem()
+ {
+ var menu = getAccessible(getAccessible(getContextMenuNode()));
+ for (var idx = 0; idx < menu.childCount; idx++) {
+ var item = menu.getChildAt(idx);
+
+ if (hasState(item, STATE_FOCUSED))
+ return getAccessible(item);
+ }
+ return null;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ var gQueue = null;
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new showContextMenu("input"));
+ gQueue.push(new selectMenuItem());
+ gQueue.push(new closeContextMenu());
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=580535"
+ title="Broken accessibility in context menus">
+ Mozilla Bug 580535
+ </a><br>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="input">
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_descrchange.html b/accessible/tests/mochitest/events/test_descrchange.html
new file mode 100644
index 000000000..d51318532
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_descrchange.html
@@ -0,0 +1,85 @@
+<html>
+
+<head>
+ <title>Accessible description change event testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function setAttr(aID, aAttr, aValue, aChecker)
+ {
+ this.eventSeq = [ aChecker ];
+ this.invoke = function setAttr_invoke()
+ {
+ getNode(aID).setAttribute(aAttr, aValue);
+ }
+
+ this.getID = function setAttr_getID()
+ {
+ return "set attr '" + aAttr + "', value '" + aValue + "'";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ //gA11yEventDumpToConsole = true; // debuggin
+
+ var gQueue = null;
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new setAttr("tst1", "aria-describedby", "display",
+ new invokerChecker(EVENT_DESCRIPTION_CHANGE, "tst1")));
+ gQueue.push(new setAttr("tst1", "title", "title",
+ new unexpectedInvokerChecker(EVENT_DESCRIPTION_CHANGE, "tst1")));
+
+ gQueue.push(new setAttr("tst2", "title", "title",
+ new invokerChecker(EVENT_NAME_CHANGE, "tst2")));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=991969"
+ title="Event not fired when description changes">
+ Bug 991969
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <button id="tst1">btn1</button>
+ <button id="tst2">btn2</button>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_docload.html b/accessible/tests/mochitest/events/test_docload.html
new file mode 100644
index 000000000..a530592ee
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_docload.html
@@ -0,0 +1,360 @@
+<html>
+
+<head>
+ <title>Accessible events testing for document</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ // Front end stuff sometimes likes to stuff things in the hidden window(s)
+ // in which case there's accessibles for that content.
+ Components.utils.import("resource://gre/modules/Services.jsm");
+
+ // Force the creation of an accessible for the hidden window's document.
+ var doc = Services.appShell.hiddenDOMWindow.document;
+ gAccService.getAccessibleFor(doc);
+
+ // The private hidden window will be lazily created that's why we need to do
+ // it here *before* loading '../events.js' or else we'll have a duplicate
+ // reorder event.
+ var privateDoc = Services.appShell.hiddenPrivateDOMWindow.document;
+
+ // Force the creation of an accessible for the private hidden window's doc.
+ gAccService.getAccessibleFor(privateDoc);
+ </script>
+
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function changeIframeSrc(aIdentifier, aURL)
+ {
+ this.DOMNode = getNode(aIdentifier);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, getAccessible(this.DOMNode)),
+ new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, getIframeDoc)
+ ];
+
+ this.invoke = function changeIframeSrc_invoke()
+ {
+ this.DOMNode.src = aURL;
+ }
+
+ this.finalCheck = function changeIframeSrc_finalCheck()
+ {
+ var accTree = {
+ role: ROLE_INTERNAL_FRAME,
+ children: [
+ {
+ role: ROLE_DOCUMENT,
+ name: aURL == "about:" ? "About:" : aURL
+ }
+ ]
+ };
+
+ testAccessibleTree(this.DOMNode, accTree);
+ }
+
+ this.getID = function changeIframeSrc_getID()
+ {
+ return "change iframe src on " + aURL;
+ }
+
+ function getIframeDoc()
+ {
+ return getAccessible(getNode(aIdentifier).contentDocument);
+ }
+ }
+
+ const kHide = 1;
+ const kShow = 2;
+ const kRemove = 3;
+ function morphIFrame(aIdentifier, aAction)
+ {
+ this.DOMNode = getNode(aIdentifier);
+ this.IFrameContainerDOMNode = this.DOMNode.parentNode;
+
+ this.eventSeq = [];
+
+ var checker = null;
+ if (aAction == kShow)
+ checker = new invokerChecker(EVENT_SHOW, this.DOMNode);
+ else
+ checker = new invokerChecker(EVENT_HIDE, this.DOMNode);
+ this.eventSeq.push(checker);
+
+ var reorderChecker =
+ new invokerChecker(EVENT_REORDER, this.IFrameContainerDOMNode);
+ this.eventSeq.push(reorderChecker);
+
+ this.invoke = function morphIFrame_invoke()
+ {
+ if (aAction == kHide) {
+ this.DOMNode.style.display = "none";
+ } else if (aAction == kShow) {
+ this.DOMNode.style.display = "block";
+ } else {
+ this.IFrameContainerDOMNode.removeChild(this.DOMNode);
+ }
+ }
+
+ this.finalCheck = function morphIFrame_finalCheck()
+ {
+ var accTree = {
+ role: ROLE_SECTION,
+ children: (aAction == kHide || aAction == kRemove) ? [ ] :
+ [
+ {
+ role: ROLE_INTERNAL_FRAME,
+ children: [
+ { role: ROLE_DOCUMENT }
+ ]
+ }
+ ]
+ };
+
+ testAccessibleTree(this.IFrameContainerDOMNode, accTree);
+ }
+
+ this.getID = function morphIFrame_getID()
+ {
+ if (aAction == kRemove)
+ return "remove iframe";
+
+ return "change display style of iframe to " +
+ ((aAction == kHide) ? "none" : "block");
+ }
+ }
+
+ function makeIFrameVisible(aID)
+ {
+ this.DOMNode = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.DOMNode.parentNode)
+ ];
+
+ this.invoke = function makeIFrameVisible_invoke()
+ {
+ this.DOMNode.style.visibility = "visible";
+ }
+
+ this.getID = function makeIFrameVisible_getID()
+ {
+ return "The accessible for DOM document loaded before it's shown shouldn't have busy state.";
+ }
+ }
+
+ function openDialogWnd(aURL)
+ {
+ // Get application root accessible.
+ var docAcc = getAccessible(document);
+ while (docAcc) {
+ this.mRootAcc = docAcc;
+ try {
+ docAcc = docAcc.parent;
+ } catch (e) {
+ ok(false, "Can't get parent for " + prettyName(docAcc));
+ throw e;
+ }
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.mRootAcc)
+ ];
+
+ this.invoke = function openDialogWnd_invoke()
+ {
+ this.mDialog = window.openDialog(aURL);
+ }
+
+ this.finalCheck = function openDialogWnd_finalCheck()
+ {
+ this.finalCheckImpl();
+ }
+
+ this.finalCheckImpl = function openDialogWnd_finalCheckImpl()
+ {
+ var accTree = {
+ role: ROLE_APP_ROOT,
+ children: [
+ {
+ role: ROLE_CHROME_WINDOW
+ },
+ {
+ role: ROLE_CHROME_WINDOW
+ },
+ {
+ role: ROLE_CHROME_WINDOW
+ },
+ {
+ role: ROLE_CHROME_WINDOW
+ }
+ ]
+ };
+
+ testAccessibleTree(this.mRootAcc, accTree);
+
+ var dlgDoc = this.mDialog.document;
+ ok(isAccessibleInCache(dlgDoc),
+ "The document accessible for '" + aURL + "' is not in cache!");
+
+ this.mDialog.close();
+
+ // close() is asynchronous.
+ SimpleTest.executeSoon(function() {
+ ok(!isAccessibleInCache(dlgDoc),
+ "The document accessible for '" + aURL + "' is in cache still!");
+ });
+ }
+
+ this.getID = function openDialogWnd_getID()
+ {
+ return "open dialog '" + aURL + "'";
+ }
+ }
+
+ function openWndShutdownDoc()
+ {
+ this.__proto__ =
+ new openDialogWnd("../events/docload_wnd.html");
+
+ var thisObj = this;
+ var docChecker = {
+ type: EVENT_HIDE,
+ get target()
+ {
+ var iframe = this.invoker.mDialog.document.getElementById("iframe");
+ this.invoker.iframeDoc = iframe.contentDocument;
+ return iframe;
+ },
+ get targetDescr()
+ {
+ return "inner iframe of docload_wnd.html document";
+ },
+ invoker: thisObj
+ };
+
+ this.eventSeq.push(docChecker);
+
+ this.finalCheck = function openWndShutdownDoc_finalCheck()
+ {
+ // After timeout after event hide for iframe was handled the document
+ // accessible for iframe's document is in cache still.
+ ok(!isAccessibleInCache(this.iframeDoc),
+ "The document accessible for iframe is in cache still after iframe hide!");
+
+ this.finalCheckImpl();
+
+ // After the window is closed all alive subdocument accessibles should
+ // be shut down.
+ ok(!isAccessibleInCache(this.iframeDoc),
+ "The document accessible for iframe is in cache still!");
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ var gQueue = null;
+
+ // Debug stuff.
+ // gA11yEventDumpID = "eventdump";
+ //gA11yEventDumpToConsole = true;
+
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new changeIframeSrc("iframe", "about:"));
+ gQueue.push(new changeIframeSrc("iframe", "about:buildconfig"));
+ gQueue.push(new morphIFrame("iframe", kHide));
+ gQueue.push(new morphIFrame("iframe", kShow));
+ gQueue.push(new morphIFrame("iframe", kRemove));
+ gQueue.push(new makeIFrameVisible("iframe2"));
+ gQueue.push(new openDialogWnd("about:"));
+ gQueue.push(new openWndShutdownDoc());
+
+ gQueue.onFinish = doLastCallTests;
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ function doLastCallTests()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // makeIFrameVisible() test, part2
+
+ // The document shouldn't have busy state (the DOM document was loaded
+ // before its accessible was created). Do this test lately to make sure
+ // the content of document accessible was created initially, prior to this
+ // the document accessible keeps busy state. The initial creation happens
+ // asynchronously after document creation, there are no events we could
+ // use to catch it.
+ var iframeDoc = getAccessible("iframe2").firstChild;
+ testStates(iframeDoc, 0, 0, STATE_BUSY);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=420845"
+ title="Fire event_reorder on any embedded frames/iframes whos document has just loaded">
+ Mozilla Bug 420845
+ </a><br>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=506206"
+ title="Fire event_reorder application root accessible">
+ Mozilla Bug 506206
+ </a><br>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=566103"
+ title="Reorganize accessible document handling">
+ Mozilla Bug 566103
+ </a><br>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=571459"
+ title="Shutdown document accessible when presshell goes away">
+ Mozilla Bug 571459
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=658185"
+ title="The DOM document loaded before it's shown shouldn't have busy state">
+ Mozilla Bug 658185
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=754165"
+ title="Fire document load events on iframes too">
+ Mozilla Bug 754165
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="testContainer"><iframe id="iframe"></iframe></div>
+ <div id="testContainer2"><iframe id="iframe2" src="about:" style="visibility: hidden;"></iframe></div>
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_docload.xul b/accessible/tests/mochitest/events/test_docload.xul
new file mode 100644
index 000000000..4b07b0e72
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_docload.xul
@@ -0,0 +1,243 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessibility Loading Document Events Test.">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../browser.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Invoker checkers.
+ function stateBusyChecker(aIsEnabled)
+ {
+ this.type = EVENT_STATE_CHANGE;
+ this.__defineGetter__("target", currentTabDocument);
+
+ this.check = function stateBusyChecker_check(aEvent)
+ {
+ var event = null;
+ try {
+ var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
+ } catch (e) {
+ ok(false, "State change event was expected");
+ }
+
+ if (!event)
+ return;
+
+ is(event.state, STATE_BUSY, "Wrong state of statechange event.");
+ is(event.isEnabled, aIsEnabled,
+ "Wrong value of state of statechange event");
+
+ testStates(event.accessible, (aIsEnabled ? STATE_BUSY : 0), 0,
+ (aIsEnabled ? 0 : STATE_BUSY), 0);
+ }
+ }
+
+ function documentReloadChecker(aIsFromUserInput)
+ {
+ this.type = EVENT_DOCUMENT_RELOAD;
+ this.__defineGetter__("target", currentTabDocument);
+
+ this.check = function documentReloadChecker_check(aEvent)
+ {
+ is(aEvent.isFromUserInput, aIsFromUserInput,
+ "Wrong value of isFromUserInput");
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers.
+
+ /**
+ * Load URI.
+ */
+ function loadURIInvoker(aURI)
+ {
+ this.invoke = function loadURIInvoker_invoke()
+ {
+ tabBrowser().loadURI(aURI);
+ }
+
+ this.eventSeq = [
+ // We don't expect state change event for busy true since things happen
+ // quickly and it's coalesced.
+ new asyncInvokerChecker(EVENT_REORDER, currentBrowser),
+ new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument),
+ new stateBusyChecker(false)
+ ];
+
+ this.getID = function loadURIInvoker_getID()
+ {
+ return "load uri " + aURI;
+ }
+ }
+
+ /**
+ * Load the document having sub document. No document loading events for
+ * nested document.
+ */
+ function loadNestedDocURIInvoker(aNestedDocURI)
+ {
+ this.__proto__ = new loadURIInvoker(aNestedDocURI);
+
+ // Remove reorder event checker since the event is likely coalesced by
+ // reorder event on Firefox UI (refer to bug 759670 for details).
+ this.eventSeq.shift();
+
+ this.unexpectedEventSeq = [
+ new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, getNestedDoc),
+ new invokerChecker(EVENT_STATE_CHANGE, getNestedDoc)
+ ];
+
+ function getNestedDoc()
+ {
+ var iframeNodes = currentTabDocument().getElementsByTagName("iframe");
+ return iframeNodes && iframeNodes.length > 0 ?
+ iframeNodes[0].firstChild : null;
+ }
+ }
+
+ /**
+ * Reload the page by F5 (isFromUserInput flag is true).
+ */
+ function userReloadInvoker()
+ {
+ this.invoke = function userReloadInvoker_invoke()
+ {
+ synthesizeKey("VK_F5", {}, browserWindow());
+ }
+
+ this.eventSeq = [
+ new documentReloadChecker(true),
+ new asyncInvokerChecker(EVENT_REORDER, currentBrowser),
+ new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument),
+ new stateBusyChecker(false)
+ ];
+
+ this.getID = function userReloadInvoker_getID()
+ {
+ return "user reload page";
+ }
+ }
+
+ /**
+ * Reload the page (isFromUserInput flag is false).
+ */
+ function reloadInvoker()
+ {
+ this.invoke = function reloadInvoker_invoke()
+ {
+ tabBrowser().reload();
+ }
+
+ this.eventSeq = [
+ new documentReloadChecker(false),
+ new asyncInvokerChecker(EVENT_REORDER, currentBrowser),
+ new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument),
+ new stateBusyChecker(false)
+ ];
+
+ this.getID = function reloadInvoker_getID()
+ {
+ return "reload page";
+ }
+ }
+
+ /**
+ * Load wrong URI what results in error page loading.
+ */
+ function loadErrorPageInvoker(aURL, aURLDescr)
+ {
+ this.invoke = function loadErrorPageInvoker_invoke()
+ {
+ tabBrowser().loadURI(aURL);
+ }
+
+ this.eventSeq = [
+ // We don't expect state change for busy true, load stopped events since
+ // things happen quickly and it's coalesced.
+ new asyncInvokerChecker(EVENT_REORDER, currentBrowser),
+ new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument),
+ new stateBusyChecker(false)
+ ];
+
+ this.getID = function loadErrorPageInvoker_getID()
+ {
+ return "load error page: '" + aURLDescr + "'";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Tests
+
+ //gA11yEventDumpToConsole = true; // debug
+ //gA11yEventDumpFeature = "parentchain:reorder";
+
+ var gQueue = null;
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ var dataURL =
+ "data:text/html,<html><body><iframe src='http://example.com'></iframe></body></html>";
+ gQueue.push(new loadNestedDocURIInvoker(dataURL));
+
+ gQueue.push(new loadURIInvoker("about:"));
+ gQueue.push(new userReloadInvoker());
+ gQueue.push(new loadURIInvoker("about:mozilla"));
+ gQueue.push(new reloadInvoker());
+ gQueue.push(new loadErrorPageInvoker("www.wronguri.wronguri",
+ "Server not found"));
+ gQueue.push(new loadErrorPageInvoker("https://nocert.example.com:443",
+ "Untrusted Connection"));
+
+ gQueue.onFinish = function() { closeBrowserWindow(); }
+ gQueue.invoke();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ openBrowserWindow(doTests);
+ ]]>
+ </script>
+
+ <vbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=566103"
+ title=" reorganize accessible document handling">
+ Mozilla Bug 566103
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=754165"
+ title="Fire document load events on iframes too">
+ Mozilla Bug 754165
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="eventdump"></vbox>
+ </vbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_docload_aria.html b/accessible/tests/mochitest/events/test_docload_aria.html
new file mode 100644
index 000000000..c5f470aee
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_docload_aria.html
@@ -0,0 +1,83 @@
+<html>
+
+<head>
+ <title>Accessible events testing for ARIA document</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function showARIADialog(aID)
+ {
+ this.dialogNode = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, this.dialogNode)
+ ];
+
+ this.invoke = function showARIADialog_invoke()
+ {
+ this.dialogNode.style.display = "block";
+ }
+
+ this.getID = function showARIADialog_getID()
+ {
+ return "show ARIA dialog";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ var gQueue = null;
+
+ // Debug stuff.
+ //gA11yEventDumpToConsole = true;
+
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new showARIADialog("dialog"));
+ gQueue.push(new showARIADialog("document"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=759833"
+ title="ARIA documents should fire document loading events">
+ Mozilla Bug 759833
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div role="dialog" id="dialog" style="display: none;">It's a dialog</div>
+ <div role="document" id="document" style="display: none;">It's a document</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_dragndrop.html b/accessible/tests/mochitest/events/test_dragndrop.html
new file mode 100644
index 000000000..cfba80a46
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_dragndrop.html
@@ -0,0 +1,110 @@
+<html>
+
+<head>
+ <title>Accessible drag and drop event testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript">
+
+ /**
+ * Do tests.
+ */
+ var gQueue = null;
+
+ // aria grabbed invoker
+ function changeGrabbed(aNodeOrID, aGrabValue)
+ {
+ this.DOMNode = getNode(aNodeOrID);
+
+ this.invoke = function changeGrabbed_invoke() {
+ if (aGrabValue != undefined) {
+ this.DOMNode.setAttribute("aria-grabbed", aGrabValue);
+ }
+ }
+
+ this.check = function changeGrabbed_check() {
+ testAttrs(aNodeOrID, {"grabbed" : aGrabValue}, true);
+ }
+
+ this.getID = function changeGrabbed_getID() {
+ return prettyName(aNodeOrID) + " aria-grabbed changed";
+ }
+ }
+
+ // aria dropeffect invoker
+ function changeDropeffect(aNodeOrID, aDropeffectValue)
+ {
+ this.DOMNode = getNode(aNodeOrID);
+
+ this.invoke = function changeDropeffect_invoke() {
+ if (aDropeffectValue != undefined) {
+ this.DOMNode.setAttribute("aria-dropeffect", aDropeffectValue);
+ }
+ }
+
+ this.check = function changeDropeffect_check() {
+ testAttrs(aNodeOrID, {"dropeffect" : aDropeffectValue}, true);
+ }
+
+ this.getID = function changeDropeffect_getID() {
+ return prettyName(aNodeOrID) + " aria-dropeffect changed";
+ }
+ }
+
+ function doTests()
+ {
+ // Test aria attribute mutation events
+ gQueue = new eventQueue(nsIAccessibleEvent.EVENT_OBJECT_ATTRIBUTE_CHANGED);
+
+ var id="grabbable";
+ gQueue.push(new changeGrabbed(id, "true"));
+ gQueue.push(new changeGrabbed(id, "false"));
+ todo(false, "uncomment this test when 472142 is fixed.");
+ //gQueue.push(new changeGrabbed(id, "undefined"));
+
+ var id="dropregion";
+ gQueue.push(new changeDropeffect(id, "copy"));
+ gQueue.push(new changeDropeffect(id, "execute"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=510441"
+ title="Add support for nsIAccessibleEvent::OBJECT_ATTRIBUTE_CHANGED">
+ Mozilla Bug 510441
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <div id="eventdump"></div>
+
+ <!-- ARIA grabbed -->
+ <div id="grabbable" role="button" aria-grabbed="foo">button</div>
+
+ <!-- ARIA dropeffect -->
+ <div id="dropregion" role="region" aria-dropeffect="none">button</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_flush.html b/accessible/tests/mochitest/events/test_flush.html
new file mode 100644
index 000000000..44a9afd94
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_flush.html
@@ -0,0 +1,77 @@
+<html>
+
+<head>
+ <title>Flush delayed events testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ SimpleTest.expectAssertions(0, 1);
+
+ var gFocusHandler = {
+ handleEvent: function(aEvent) {
+ switch (this.count) {
+ case 0:
+ is(aEvent.DOMNode, getNode("input1"),
+ "Focus event for input1 was expected!");
+ getAccessible("input2").takeFocus();
+ break;
+
+ case 1:
+ is(aEvent.DOMNode, getNode("input2"),
+ "Focus event for input2 was expected!");
+
+ unregisterA11yEventListener(EVENT_FOCUS, gFocusHandler);
+ SimpleTest.finish();
+ break;
+
+ default:
+ ok(false, "Wrong focus event!");
+ }
+
+ this.count++;
+ },
+
+ count: 0
+ };
+
+ function doTests()
+ {
+ registerA11yEventListener(EVENT_FOCUS, gFocusHandler);
+
+ getAccessible("input1").takeFocus();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=477551"
+ title="DocAccessible::FlushPendingEvents isn't robust">
+ Mozilla Bug 477551
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="input1">
+ <input id="input2">
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html b/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html
new file mode 100644
index 000000000..4cd57fe3b
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=429547
+-->
+<head>
+ <title>aria-activedescendant focus tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ //gA11yEventDumpToConsole = true; // debugging
+
+ function changeARIAActiveDescendant(aID, aItemID)
+ {
+ this.eventSeq = [
+ new focusChecker(aItemID)
+ ];
+
+ this.invoke = function changeARIAActiveDescendant_invoke()
+ {
+ getNode(aID).setAttribute("aria-activedescendant", aItemID);
+ }
+
+ this.getID = function changeARIAActiveDescendant_getID()
+ {
+ return "change aria-activedescendant on " + aItemID;
+ }
+ }
+
+ function insertItemNFocus(aID, aNewItemID)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, aNewItemID),
+ new focusChecker(aNewItemID)
+ ];
+
+ this.invoke = function insertItemNFocus_invoke()
+ {
+ var container = getNode(aID);
+ var itemNode = document.createElement("div");
+ itemNode.setAttribute("id", aNewItemID);
+ itemNode.textContent = aNewItemID;
+ container.appendChild(itemNode);
+
+ container.setAttribute("aria-activedescendant", aNewItemID);
+ }
+
+ this.getID = function insertItemNFocus_getID()
+ {
+ return "insert new node and focus it with ID: " + aNewItemID;
+ }
+ }
+
+ var gQueue = null;
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new synthFocus("listbox", new focusChecker("item1")));
+ gQueue.push(new changeARIAActiveDescendant("listbox", "item2"));
+ gQueue.push(new changeARIAActiveDescendant("listbox", "item3"));
+
+ gQueue.push(new synthFocus("combobox_entry", new focusChecker("combobox_entry")));
+ gQueue.push(new changeARIAActiveDescendant("combobox", "combobox_option2"));
+
+ todo(false, "No focus for inserted element, bug 687011");
+ //gQueue.push(new insertItemNFocus("listbox", "item4"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=429547"
+ title="Support aria-activedescendant usage in nsIAccesible::TakeFocus()">
+ Mozilla Bug 429547
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=761102"
+ title="Focus may be missed when ARIA active-descendant is changed on active composite widget">
+ Mozilla Bug 761102
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div role="listbox" aria-activedescendant="item1" id="listbox" tabindex="1"
+ aria-owns="item3">
+ <div role="listitem" id="item1">item1</div>
+ <div role="listitem" id="item2">item2</div>
+ </div>
+ <div role="listitem" id="item3">item3</div>
+
+ <div role="combobox" id="combobox">
+ <input id="combobox_entry">
+ <ul>
+ <li role="option" id="combobox_option1">option1</li>
+ <li role="option" id="combobox_option2">option2</li>
+ </ul>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_focus_autocomplete.xul b/accessible/tests/mochitest/events/test_focus_autocomplete.xul
new file mode 100644
index 000000000..2a32a6587
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_autocomplete.xul
@@ -0,0 +1,518 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<!-- Firefox searchbar -->
+<?xml-stylesheet href="chrome://browser/content/browser.css"
+ type="text/css"?>
+<!-- SeaMonkey searchbar -->
+<?xml-stylesheet href="chrome://navigator/content/navigator.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible focus event testing">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript"
+ src="../autocomplete.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Hacky stuffs
+
+ // This is the hacks needed to use a searchbar without browser.js.
+ function getBrowser()
+ {
+ return {
+ mCurrentBrowser: { engines: new Array() }
+ };
+ }
+
+ var BrowserSearch = {
+ updateOpenSearchBadge: function() {}
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function loadFormAutoComplete(aIFrameID)
+ {
+ this.iframeNode = getNode(aIFrameID);
+ this.iframe = getAccessible(aIFrameID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.iframe)
+ ];
+
+ this.invoke = function loadFormAutoComplete_invoke()
+ {
+ var url = "data:text/html,<html><body><form id='form'>" +
+ "<input id='input' name='a11ytest-formautocomplete'>" +
+ "</form></body></html>";
+ this.iframeNode.setAttribute("src", url);
+ }
+
+ this.getID = function loadFormAutoComplete_getID()
+ {
+ return "load form autocomplete page";
+ }
+ }
+
+ function initFormAutoCompleteBy(aIFrameID, aAutoCompleteValue)
+ {
+ this.iframe = getAccessible(aIFrameID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.iframe)
+ ];
+
+ this.invoke = function initFormAutoCompleteBy_invoke()
+ {
+ var iframeDOMDoc = getIFrameDOMDoc(aIFrameID);
+
+ var inputNode = iframeDOMDoc.getElementById("input");
+ inputNode.value = aAutoCompleteValue;
+ var formNode = iframeDOMDoc.getElementById("form");
+ formNode.submit();
+ }
+
+ this.getID = function initFormAutoCompleteBy_getID()
+ {
+ return "init form autocomplete by '" + aAutoCompleteValue + "'";
+ }
+ }
+
+ function loadHTML5ListAutoComplete(aIFrameID)
+ {
+ this.iframeNode = getNode(aIFrameID);
+ this.iframe = getAccessible(aIFrameID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.iframe)
+ ];
+
+ this.invoke = function loadHTML5ListAutoComplete_invoke()
+ {
+ var url = "data:text/html,<html><body>" +
+ "<datalist id='cities'><option>hello</option><option>hi</option></datalist>" +
+ "<input id='input' list='cities'>" +
+ "</body></html>";
+ this.iframeNode.setAttribute("src", url);
+ }
+
+ this.getID = function loadHTML5ListAutoComplete_getID()
+ {
+ return "load HTML5 list autocomplete page";
+ }
+ }
+
+ function removeChar(aID, aCheckerOrEventSeq)
+ {
+ this.__proto__ = new synthAction(aID, aCheckerOrEventSeq);
+
+ this.invoke = function removeChar_invoke()
+ {
+ synthesizeKey("VK_LEFT", { shiftKey: true });
+ synthesizeKey("VK_DELETE", {});
+ }
+
+ this.getID = function removeChar_getID()
+ {
+ return "remove char on " + prettyName(aID);
+ }
+ }
+
+ function replaceOnChar(aID, aChar, aCheckerOrEventSeq)
+ {
+ this.__proto__ = new synthAction(aID, aCheckerOrEventSeq);
+
+ this.invoke = function replaceOnChar_invoke()
+ {
+ this.DOMNode.select();
+ synthesizeKey(aChar, {});
+ }
+
+ this.getID = function replaceOnChar_getID()
+ {
+ return "replace on char '" + aChar + "' for" + prettyName(aID);
+ }
+ }
+
+ function focusOnMouseOver(aIDFunc, aIDFuncArg)
+ {
+ this.eventSeq = [ new focusChecker(aIDFunc, aIDFuncArg) ];
+
+ this.invoke = function focusOnMouseOver_invoke()
+ {
+ this.id = aIDFunc.call(null, aIDFuncArg);
+ this.node = getNode(this.id);
+ this.window = this.node.ownerDocument.defaultView;
+
+ if (this.node.localName == "tree") {
+ var tree = getAccessible(this.node);
+ var accessible = getAccessible(this.id);
+ if (tree != accessible) {
+ var itemX = {}, itemY = {}, treeX = {}, treeY = {};
+ accessible.getBounds(itemX, itemY, {}, {});
+ tree.getBounds(treeX, treeY, {}, {});
+ this.x = itemX.value - treeX.value;
+ this.y = itemY.value - treeY.value;
+ }
+ }
+
+ // Generate mouse move events in timeouts until autocomplete popup list
+ // doesn't have it, the reason is do that because autocomplete popup
+ // ignores mousemove events firing in too short range.
+ synthesizeMouse(this.node, this.x, this.y, { type: "mousemove" });
+ this.doMouseMoveFlood(this);
+ }
+
+ this.finalCheck = function focusOnMouseOver_getID()
+ {
+ this.isFlooding = false;
+ }
+
+ this.getID = function focusOnMouseOver_getID()
+ {
+ return "mouse over on " + prettyName(aIDFunc.call(null, aIDFuncArg));
+ }
+
+ this.doMouseMoveFlood = function focusOnMouseOver_doMouseMoveFlood(aThis)
+ {
+ synthesizeMouse(aThis.node, aThis.x + 1, aThis.y + 1,
+ { type: "mousemove" }, aThis.window);
+
+ if (aThis.isFlooding)
+ aThis.window.setTimeout(aThis.doMouseMoveFlood, 0, aThis);
+ }
+
+ this.id = null;
+ this.node = null;
+ this.window = null;
+
+ this.isFlooding = true;
+ this.x = 1;
+ this.y = 1;
+ }
+
+ function selectByClick(aIDFunc, aIDFuncArg,
+ aFocusTargetFunc, aFocusTargetFuncArg)
+ {
+ this.eventSeq = [ new focusChecker(aFocusTargetFunc, aFocusTargetFuncArg) ];
+
+ this.invoke = function selectByClick_invoke()
+ {
+ var id = aIDFunc.call(null, aIDFuncArg);
+ var node = getNode(id);
+ var targetWindow = node.ownerDocument.defaultView;
+
+ var x = 0, y = 0;
+ if (node.localName == "tree") {
+ var tree = getAccessible(node);
+ var accessible = getAccessible(id);
+ if (tree != accessible) {
+ var itemX = {}, itemY = {}, treeX = {}, treeY = {};
+ accessible.getBounds(itemX, itemY, {}, {});
+ tree.getBounds(treeX, treeY, {}, {});
+ x = itemX.value - treeX.value;
+ y = itemY.value - treeY.value;
+ }
+ }
+
+ synthesizeMouseAtCenter(node, {}, targetWindow);
+ }
+
+ this.getID = function selectByClick_getID()
+ {
+ return "select by click " + prettyName(aIDFunc.call(null, aIDFuncArg));
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Target getters
+
+ function getItem(aItemObj)
+ {
+ var autocomplete = aItemObj.autocomplete;
+ var autocompleteNode = aItemObj.autocompleteNode;
+
+ // XUL searchbar
+ if (autocompleteNode.localName == "searchbar") {
+ var popupNode = autocompleteNode._popup;
+ if (popupNode) {
+ var list = getAccessible(popupNode);
+ return list.getChildAt(aItemObj.index);
+ }
+ }
+
+ // XUL autocomplete
+ var popupNode = autocompleteNode.popup;
+ if (!popupNode) {
+ // HTML form autocomplete
+ var controller = Components.classes["@mozilla.org/autocomplete/controller;1"].
+ getService(Components.interfaces.nsIAutoCompleteController);
+ popupNode = controller.input.popup.QueryInterface(nsIDOMNode);
+ }
+
+ if (popupNode) {
+ if ("richlistbox" in popupNode) {
+ var list = getAccessible(popupNode.richlistbox);
+ return list.getChildAt(aItemObj.index);
+ }
+
+ var list = getAccessible(popupNode.tree);
+ return list.getChildAt(aItemObj.index + 1);
+ }
+ }
+
+ function getTextEntry(aID)
+ {
+ // For form autocompletes the autocomplete widget and text entry widget
+ // is the same widget, for XUL autocompletes the text entry is a first
+ // child.
+ var localName = getNode(aID).localName;
+
+ // XUL autocomplete
+ if (localName == "textbox")
+ return getAccessible(aID).firstChild;
+
+ // HTML form autocomplete
+ if (localName == "input")
+ return getAccessible(aID);
+
+ // XUL searchbar
+ if (localName == "searchbar")
+ return getAccessible(getNode(aID).textbox.inputField);
+
+ return null;
+ }
+
+ function itemObj(aID, aIdx)
+ {
+ this.autocompleteNode = getNode(aID);
+
+ this.autocomplete = this.autocompleteNode.localName == "searchbar" ?
+ getAccessible(this.autocompleteNode.textbox) :
+ getAccessible(this.autocompleteNode);
+
+ this.index = aIdx;
+ }
+
+ function getIFrameDOMDoc(aIFrameID)
+ {
+ return getNode(aIFrameID).contentDocument;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test helpers
+
+ function queueAutoCompleteTests(aID)
+ {
+ // focus autocomplete text entry
+ gQueue.push(new synthFocus(aID, new focusChecker(getTextEntry, aID)));
+
+ // open autocomplete popup
+ gQueue.push(new synthDownKey(aID, new nofocusChecker()));
+
+ // select second option ('hi' option), focus on it
+ gQueue.push(new synthUpKey(aID,
+ new focusChecker(getItem, new itemObj(aID, 1))));
+
+ // choose selected option, focus on text entry
+ gQueue.push(new synthEnterKey(aID, new focusChecker(getTextEntry, aID)));
+
+ // remove char, autocomplete popup appears
+ gQueue.push(new removeChar(aID, new nofocusChecker()));
+
+ // select first option ('hello' option), focus on it
+ gQueue.push(new synthDownKey(aID,
+ new focusChecker(getItem, new itemObj(aID, 0))));
+
+ // mouse move on second option ('hi' option), focus on it
+ gQueue.push(new focusOnMouseOver(getItem, new itemObj(aID, 1)));
+
+ // autocomplete popup updated (no selected item), focus on textentry
+ gQueue.push(new synthKey(aID, "e", null, new focusChecker(getTextEntry, aID)));
+
+ // select first option ('hello' option), focus on it
+ gQueue.push(new synthDownKey(aID,
+ new focusChecker(getItem, new itemObj(aID, 0))));
+
+ // popup gets hidden, focus on textentry
+ gQueue.push(new synthRightKey(aID, new focusChecker(getTextEntry, aID)));
+
+ // popup gets open, no focus
+ gQueue.push(new synthOpenComboboxKey(aID, new nofocusChecker()));
+
+ // select first option again ('hello' option), focus on it
+ gQueue.push(new synthDownKey(aID,
+ new focusChecker(getItem, new itemObj(aID, 0))));
+
+ // no option is selected, focus on text entry
+ gQueue.push(new synthUpKey(aID, new focusChecker(getTextEntry, aID)));
+
+ // close popup, no focus
+ gQueue.push(new synthEscapeKey(aID, new nofocusChecker()));
+
+ // autocomplete popup appears (no selected item), focus stays on textentry
+ gQueue.push(new replaceOnChar(aID, "h", new nofocusChecker()));
+
+ // mouse move on first option ('hello' option), focus on it
+ gQueue.push(new focusOnMouseOver(getItem, new itemObj(aID, 0)));
+
+ // click first option ('hello' option), popup closes, focus on text entry
+ gQueue.push(new selectByClick(getItem, new itemObj(aID, 0), getTextEntry, aID));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Tests
+
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var gInitQueue = null;
+ function initTests()
+ {
+ if (SEAMONKEY || MAC) {
+ todo(false, "Skipping this test on SeaMonkey ftb. (Bug 718237), and on Mac (bug 746177)");
+ shutdownAutoComplete();
+ SimpleTest.finish();
+ return;
+ }
+
+ gInitQueue = new eventQueue();
+ gInitQueue.push(new loadFormAutoComplete("iframe"));
+ gInitQueue.push(new initFormAutoCompleteBy("iframe", "hello"));
+ gInitQueue.push(new initFormAutoCompleteBy("iframe", "hi"));
+ gInitQueue.push(new loadHTML5ListAutoComplete("iframe2"));
+ gInitQueue.onFinish = function initQueue_onFinish()
+ {
+ SimpleTest.executeSoon(doTests);
+ return DO_NOT_FINISH_TEST;
+ }
+ gInitQueue.invoke();
+ }
+
+ var gQueue = null;
+ function doTests()
+ {
+ // Test focus events.
+ gQueue = new eventQueue();
+
+ ////////////////////////////////////////////////////////////////////////////
+ // tree popup autocomplete tests
+ queueAutoCompleteTests("autocomplete");
+
+ ////////////////////////////////////////////////////////////////////////////
+ // richlistbox popup autocomplete tests
+ queueAutoCompleteTests("richautocomplete");
+
+ ////////////////////////////////////////////////////////////////////////////
+ // HTML form autocomplete tests
+ queueAutoCompleteTests(getIFrameDOMDoc("iframe").getElementById("input"));
+
+ ////////////////////////////////////////////////////////////////////////////
+ // HTML5 list autocomplete tests
+ queueAutoCompleteTests(getIFrameDOMDoc("iframe2").getElementById("input"));
+
+ ////////////////////////////////////////////////////////////////////////////
+ // searchbar tests
+
+ // focus searchbar, focus on text entry
+ gQueue.push(new synthFocus("searchbar",
+ new focusChecker(getTextEntry, "searchbar")));
+ // open search engine popup, no focus
+ gQueue.push(new synthOpenComboboxKey("searchbar", new nofocusChecker()));
+ // select first item, focus on it
+ gQueue.push(new synthDownKey("searchbar",
+ new focusChecker(getItem, new itemObj("searchbar", 0))));
+ // mouse over on second item, focus on it
+ gQueue.push(new focusOnMouseOver(getItem, new itemObj("searchbar", 1)));
+ // press enter key, focus on text entry
+ gQueue.push(new synthEnterKey("searchbar",
+ new focusChecker(getTextEntry, "searchbar")));
+ // click on search button, open popup, focus goes to document
+ var searchBtn = getAccessible(getNode("searchbar").searchButton);
+ gQueue.push(new synthClick(searchBtn, new focusChecker(document)));
+ // select first item, focus on it
+ gQueue.push(new synthDownKey("searchbar",
+ new focusChecker(getItem, new itemObj("searchbar", 0))));
+ // close popup, focus goes on document
+ gQueue.push(new synthEscapeKey("searchbar", new focusChecker(document)));
+
+ gQueue.onFinish = function()
+ {
+ // unregister 'test-a11y-search' autocomplete search
+ shutdownAutoComplete();
+ }
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ // Register 'test-a11y-search' autocomplete search.
+ // XPFE AutoComplete needs to register early.
+ initAutoComplete([ "hello", "hi" ],
+ [ "Beep beep'm beep beep yeah", "Baby you can drive my car" ]);
+
+ addA11yLoadEvent(initTests);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=383759"
+ title="Focus event inconsistent for search box autocomplete">
+ Mozilla Bug 383759
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958"
+ title="Rework accessible focus handling">
+ Mozilla Bug 673958
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=559766"
+ title="Add accessibility support for @list on HTML input and for HTML datalist">
+ Mozilla Bug 559766
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <textbox id="autocomplete" type="autocomplete"
+ autocompletesearch="test-a11y-search"/>
+
+ <textbox id="richautocomplete" type="autocomplete"
+ autocompletesearch="test-a11y-search"
+ autocompletepopup="richpopup"/>
+ <panel id="richpopup" type="autocomplete-richlistbox" noautofocus="true"/>
+
+ <iframe id="iframe"/>
+
+ <iframe id="iframe2"/>
+
+ <searchbar id="searchbar"/>
+
+ <vbox id="eventdump"/>
+ </vbox>
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_focus_browserui.xul b/accessible/tests/mochitest/events/test_focus_browserui.xul
new file mode 100644
index 000000000..bd621ebf2
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_browserui.xul
@@ -0,0 +1,149 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessibility Loading Document Events Test.">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../browser.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Helpers
+
+ function inputInDocument()
+ {
+ var tabdoc = currentTabDocument();
+ return tabdoc.getElementById("input");
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function loadURI(aURI)
+ {
+ this.invoke = function loadURI_invoke()
+ {
+ tabBrowser().loadURI(aURI);
+ }
+
+ this.eventSeq = [
+ new focusChecker(currentTabDocument)
+ ];
+
+ this.getID = function loadURI_getID()
+ {
+ return "load uri " + aURI;
+ }
+ }
+
+ function goBack()
+ {
+ this.invoke = function goBack_invoke()
+ {
+ tabBrowser().goBack();
+ }
+
+ this.eventSeq = [
+ new focusChecker(inputInDocument)
+ ];
+
+ this.getID = function goBack_getID()
+ {
+ return "go back one page in history ";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Testing
+
+ var gInputDocURI = "data:text/html,<html><input id='input'></html>";
+ var gButtonDocURI = "data:text/html,<html><input id='input' type='button' value='button'></html>";
+
+ //gA11yEventDumpToConsole = true; // debug
+
+ var gQueue = null;
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ var tabDocument = currentTabDocument();
+ var input = inputInDocument();
+
+ // move focus to input inside tab document
+ gQueue.push(new synthTab(tabDocument, new focusChecker(input),
+ browserWindow()));
+
+ // open new url, focus moves to new document
+ gQueue.push(new loadURI(gButtonDocURI));
+
+ // back one page in history, moves moves on input of tab document
+ gQueue.push(new goBack());
+
+ // open new tab, focus moves to urlbar
+ gQueue.push(new synthKey(tabDocument, "t", { accelKey: true, window: browserWindow() },
+ new focusChecker(urlbarInput)));
+
+ // close open tab, focus goes on input of tab document
+ gQueue.push(new synthKey(tabDocument, "w", { accelKey: true, window: browserWindow() },
+ new focusChecker(inputInDocument)));
+
+ gQueue.onFinish = function()
+ {
+ closeBrowserWindow();
+ }
+ gQueue.invoke();
+ }
+
+ if (navigator.oscpu.startsWith("Windows NT 6.1") || navigator.oscpu.startsWith("Windows NT 6.2")) {
+ todo(false, "fix the leak!");
+ } else {
+ SimpleTest.waitForExplicitFinish();
+ openBrowserWindow(doTests, gInputDocURI);
+ }
+ ]]>
+ </script>
+
+ <vbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=644452"
+ title="Focus not set when switching to cached document with back or forward if anything other than the document was last focused">
+ Mozilla Bug 644452
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=665412"
+ title="Broken focus when returning to editable text field after closing a tab while focused in the Navigation toolbar">
+ Mozilla Bug 665412
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958"
+ title="Rework accessible focus handling">
+ Mozilla Bug 673958
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="eventdump"></vbox>
+ </vbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_focus_canvas.html b/accessible/tests/mochitest/events/test_focus_canvas.html
new file mode 100644
index 000000000..dcccb08e0
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_canvas.html
@@ -0,0 +1,61 @@
+<html>
+
+<head>
+ <title>Accessible focus testing in canvas subdom</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ //gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new synthFocus("button"));
+ gQueue.push(new synthTab("button", new focusChecker("textbox")));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ title="Expose content in Canvas element"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=495912">
+ Mozilla Bug 495912
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <canvas>
+ <input id="button" type="button">
+ <input id="textbox">
+ </canvas>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_focus_contextmenu.xul b/accessible/tests/mochitest/events/test_focus_contextmenu.xul
new file mode 100644
index 000000000..0cf62b912
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_contextmenu.xul
@@ -0,0 +1,99 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Context nenu focus testing">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var winLowerThanVista = navigator.platform.indexOf("Win") == 0;
+ if (winLowerThanVista) {
+ var version = Components.classes["@mozilla.org/system-info;1"]
+ .getService(Components.interfaces.nsIPropertyBag2)
+ .getProperty("version");
+ version = parseFloat(version);
+ winLowerThanVista = !(version >= 6.0);
+ }
+
+ var gQueue = null;
+ function doTests()
+ {
+ // bug 746183 - Whole file times out on OS X
+ if (MAC || winLowerThanVista) {
+ todo(false, "Reenable on mac after fixing bug 746183!");
+ SimpleTest.finish();
+ return;
+ }
+
+ // Test focus events.
+ gQueue = new eventQueue();
+
+ gQueue.push(new synthFocus("button"));
+ gQueue.push(new synthContextMenu("button",
+ new invokerChecker(EVENT_MENUPOPUP_START, "contextmenu")));
+ gQueue.push(new synthEscapeKey("contextmenu", new focusChecker("button")));
+
+ gQueue.push(new synthContextMenu("button",
+ new invokerChecker(EVENT_MENUPOPUP_START, "contextmenu")));
+ gQueue.push(new synthDownKey("contextmenu", new focusChecker("item1")));
+ gQueue.push(new synthDownKey("item1", new focusChecker("item2")));
+ gQueue.push(new synthRightKey("item2", new focusChecker("item2.1")));
+ if (WIN) {
+ todo(false, "synthEscapeKey for item2.1 and item2 disabled due to bug 691580");
+ } else {
+ gQueue.push(new synthEscapeKey("item2.1", new focusChecker("item2")));
+ gQueue.push(new synthEscapeKey("item2", new focusChecker("button")));
+ }
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958"
+ title="Rework accessible focus handling">
+ Mozilla Bug 673958
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <button id="button" context="contextmenu" label="button"/>
+ <menupopup id="contextmenu">
+ <menuitem id="item1" label="item1"/>
+ <menu id="item2" label="item2">
+ <menupopup>
+ <menuitem id="item2.1" label="item2.1"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+
+ <vbox id="eventdump"/>
+ </vbox>
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_focus_controls.html b/accessible/tests/mochitest/events/test_focus_controls.html
new file mode 100644
index 000000000..a18832a8f
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_controls.html
@@ -0,0 +1,75 @@
+<html>
+
+<head>
+ <title>Accessible focus testing on HTML controls</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ //gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+ function doTests()
+ {
+ gQueue = new eventQueue(EVENT_FOCUS);
+
+ gQueue.push(new synthFocus("textbox"));
+ gQueue.push(new synthFocus("textarea"));
+ gQueue.push(new synthFocus("button1"));
+ gQueue.push(new synthFocus("button2"));
+ gQueue.push(new synthFocus("checkbox"));
+ gQueue.push(new synthFocus("radio1"));
+ gQueue.push(new synthDownKey("radio1", new focusChecker("radio2")));
+
+ // no focus events for checkbox or radio inputs when they are checked
+ // programmatically
+ gQueue.push(new changeCurrentItem("checkbox"));
+ gQueue.push(new changeCurrentItem("radio1"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958"
+ title="Rework accessible focus handling">
+ Mozilla Bug 673958
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="textbox">
+ <textarea id="textarea"></textarea>
+
+ <input id="button1" type="button" value="button">
+ <button id="button2">button</button>
+ <input id="checkbox" type="checkbox">
+ <input id="radio1" type="radio" name="radiogroup">
+ <input id="radio2" type="radio" name="radiogroup">
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_focus_dialog.html b/accessible/tests/mochitest/events/test_focus_dialog.html
new file mode 100644
index 000000000..9d88b0cb4
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_dialog.html
@@ -0,0 +1,164 @@
+<html>
+
+<head>
+ <title>Accessible focus testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ function openCloseDialog(aID)
+ {
+ this.eventSeq = [
+ new focusChecker(getNode(aID))
+ ];
+
+ this.invoke = function openCloseDialog_invoke()
+ {
+ var wnd = window.open("focus.html");
+ wnd.close();
+ }
+
+ this.getID = function openCloseDialog_getID()
+ {
+ return "Open close dialog while focus on " + prettyName(aID);
+ }
+ }
+
+ var gDialogWnd = null;
+ function getDialogDocument()
+ {
+ return gDialogWnd.document;
+ }
+
+ function openDialog(aID)
+ {
+ this.eventSeq = [
+ new focusChecker(getDialogDocument)
+ ];
+
+ this.invoke = function openDialog_invoke()
+ {
+ gDialogWnd = window.open("focus.html");
+ }
+
+ this.getID = function openDialog_getID()
+ {
+ return "Open dialog while focus on " + prettyName(aID);
+ }
+ }
+
+ function closeDialog(aID)
+ {
+ this.eventSeq = [
+ new focusChecker(aID)
+ ];
+
+ this.invoke = function closeDialog_invoke()
+ {
+ gDialogWnd.close();
+ }
+
+ this.getID = function closeDialog_getID()
+ {
+ return "Close dialog while focus on " + prettyName(aID);
+ }
+ }
+
+ function showNFocusAlertDialog()
+ {
+ this.ID = "alertdialog";
+ this.DOMNode = getNode(this.ID);
+
+ this.invoke = function showNFocusAlertDialog_invoke()
+ {
+ document.getElementById(this.ID).style.display = 'block';
+ document.getElementById(this.ID).focus();
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, this.DOMNode),
+ new focusChecker(this.DOMNode)
+ ];
+
+ this.getID = function showNFocusAlertDialog_getID()
+ {
+ return "Show and focus alert dialog " + prettyName(this.ID);
+ }
+ }
+
+ /**
+ * Do tests.
+ */
+
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTests()
+ {
+ gQueue = new eventQueue(EVENT_FOCUS);
+
+ gQueue.push(new synthFocus("button"));
+ gQueue.push(new openDialog("button"));
+ gQueue.push(new closeDialog("button"));
+
+ var frameNode = getNode("editabledoc");
+ gQueue.push(new synthFocusOnFrame(frameNode));
+ gQueue.push(new openCloseDialog(frameNode.contentDocument));
+
+ gQueue.push(new showNFocusAlertDialog());
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=551679"
+ title="focus is not fired for focused document when switching between windows">
+ Mozilla Bug 551679
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=580464"
+ title="Accessible focus incorrect after JS focus() but correct after switching apps or using menu bar">
+ Mozilla Bug 580464
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <button id="button">button</button>
+ <iframe id="editabledoc" src="focus.html"></iframe>
+
+ <div id="alertdialog" style="display: none" tabindex="-1" role="alertdialog" aria-labelledby="title2" aria-describedby="desc2">
+ <div id="title2">Blah blah</div>
+ <div id="desc2">Woof woof woof.</div>
+ <button>Close</button>
+ </div>
+
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_focus_doc.html b/accessible/tests/mochitest/events/test_focus_doc.html
new file mode 100644
index 000000000..bd4934d84
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_doc.html
@@ -0,0 +1,95 @@
+<html>
+
+<head>
+ <title>Accessible document focus event testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ var gQueue = null;
+
+ //var gA11yEventDumpID = "eventdump";
+ //gA11yEventDumpToConsole = true;
+
+ function doTests()
+ {
+ // setup
+ var frameDoc = document.getElementById("iframe").contentDocument;
+ frameDoc.designMode = "on";
+ var frameDocAcc = getAccessible(frameDoc, [nsIAccessibleDocument]);
+ var buttonAcc = getAccessible("b1");
+
+ var frame2Doc = document.getElementById("iframe2").contentDocument;
+ var frame2Input = frame2Doc.getElementById("input");
+ var frame2DocAcc = getAccessible(frame2Doc);
+ var frame2InputAcc = getAccessible(frame2Input);
+
+ // Test focus events.
+ gQueue = new eventQueue();
+
+ // try to give focus to contentEditable frame twice to cover bug 512059
+ gQueue.push(new synthFocus(buttonAcc));
+ gQueue.push(new synthTab(frameDocAcc, new focusChecker(frameDocAcc)));
+ gQueue.push(new synthFocus(buttonAcc));
+ gQueue.push(new synthTab(frameDocAcc, new focusChecker(frameDocAcc)));
+
+ // focus on not editable document
+ gQueue.push(new synthFocus(frame2InputAcc));
+ gQueue.push(new synthShiftTab(frame2DocAcc, new focusChecker(frame2DocAcc)));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=512058"
+ title="Can't set focus to designMode document via accessibility APIs">
+ Mozilla Bug 512058
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=512059"
+ title="Accessibility focus event never fired for designMode document after the first focus">
+ Mozilla Bug 512059
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=618046"
+ title="No focus change event when Shift+Tab at top of screen">
+ Mozilla Bug 618046
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="eventdump"></div>
+
+ <div id="testContainer">
+ <button id="b1">a button</button>
+ <iframe id="iframe" src="about:blank"></iframe>
+ <button id="b2">a button</button>
+ <iframe id="iframe2" src="data:text/html,<html><input id='input'></html>"></iframe>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_focus_general.html b/accessible/tests/mochitest/events/test_focus_general.html
new file mode 100644
index 000000000..e881a5a4f
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_general.html
@@ -0,0 +1,179 @@
+<html>
+
+<head>
+ <title>Accessible focus testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ function focusElmWhileSubdocIsFocused(aID)
+ {
+ this.DOMNode = getNode(aID);
+
+ this.invoke = function focusElmWhileSubdocIsFocused_invoke()
+ {
+ this.DOMNode.focus();
+ }
+
+ this.eventSeq = [
+ new focusChecker(this.DOMNode)
+ ];
+
+ this.unexpectedEventSeq = [
+ new invokerChecker(EVENT_FOCUS, this.DOMNode.ownerDocument)
+ ];
+
+ this.getID = function focusElmWhileSubdocIsFocused_getID()
+ {
+ return "Focus element while subdocument is focused " + prettyName(aID);
+ }
+ }
+
+ function imageMapChecker(aID)
+ {
+ var node = getNode(aID);
+ this.type = EVENT_FOCUS;
+ this.match = function imageMapChecker_match(aEvent)
+ {
+ return aEvent.DOMNode == node;
+ }
+ }
+
+ function topMenuChecker()
+ {
+ this.type = EVENT_FOCUS;
+ this.match = function topMenuChecker_match(aEvent)
+ {
+ return aEvent.accessible.role == ROLE_PARENT_MENUITEM;
+ }
+ }
+
+ function contextMenuChecker()
+ {
+ this.type = EVENT_MENUPOPUP_START;
+ this.match = function contextMenuChecker_match(aEvent)
+ {
+ return aEvent.accessible.role == ROLE_MENUPOPUP;
+ }
+ }
+
+ function focusContextMenuItemChecker()
+ {
+ this.__proto__ = new focusChecker();
+
+ this.match = function focusContextMenuItemChecker_match(aEvent)
+ {
+ return aEvent.accessible.role == ROLE_MENUITEM;
+ }
+ }
+
+ /**
+ * Do tests.
+ */
+
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTests()
+ {
+ var frameDoc = document.getElementById("iframe").contentDocument;
+
+ var editableDoc = document.getElementById('editabledoc').contentDocument;
+ editableDoc.designMode = 'on';
+
+ gQueue = new eventQueue();
+
+ gQueue.push(new synthFocus("editablearea"));
+ gQueue.push(new synthFocus("navarea"));
+ gQueue.push(new synthTab("navarea", new focusChecker(frameDoc)));
+ gQueue.push(new focusElmWhileSubdocIsFocused("link"));
+
+ gQueue.push(new synthTab(editableDoc, new focusChecker(editableDoc)));
+ if (WIN || LINUX) {
+ // Alt key is used to active menubar and focus menu item on Windows,
+ // other platforms requires setting a ui.key.menuAccessKeyFocuses
+ // preference.
+ gQueue.push(new toggleTopMenu(editableDoc, new topMenuChecker()));
+ gQueue.push(new toggleTopMenu(editableDoc, new focusChecker(editableDoc)));
+ }
+ gQueue.push(new synthContextMenu(editableDoc, new contextMenuChecker()));
+ gQueue.push(new synthDownKey(editableDoc, new focusContextMenuItemChecker()));
+ gQueue.push(new synthEscapeKey(editableDoc, new focusChecker(editableDoc)));
+ if (SEAMONKEY) {
+ todo(false, "shift tab from editable document fails on (Windows) SeaMonkey! (Bug 718235)");
+ } else {
+ if (LINUX || MAC)
+ todo(false, "shift tab from editable document fails on linux and Mac, bug 746519!");
+ else
+ gQueue.push(new synthShiftTab("link", new focusChecker("link")));
+ } // ! SEAMONKEY
+
+ gQueue.push(new synthFocus("a", new imageMapChecker("a")));
+ gQueue.push(new synthFocus("b", new imageMapChecker("b")));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=352220"
+ title="Inconsistent focus events when returning to a document frame">
+ Mozilla Bug 352220
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=550338"
+ title="Broken focus when returning to editable documents from menus">
+ Mozilla Bug 550338
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958"
+ title="Rework accessible focus handling">
+ Mozilla Bug 673958
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=961696"
+ title="Accessible object:state-changed:focused events for imagemap links are broken">
+ Mozilla Bug 961696
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="editablearea" contentEditable="true">editable area</div>
+ <div id="navarea" tabindex="0">navigable area</div>
+ <iframe id="iframe" src="data:text/html,<html></html>"></iframe>
+ <a id="link" href="">link</a>
+ <iframe id="editabledoc" src="about:blank"></iframe>
+
+ <map name="atoz_map">
+ <area id="a" coords="0,0,13,14" shape="rect">
+ <area id="b" coords="17,0,30,14" shape="rect">
+ </map>
+ <img width="447" height="15" usemap="#atoz_map" src="../letters.gif">
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_focus_general.xul b/accessible/tests/mochitest/events/test_focus_general.xul
new file mode 100644
index 000000000..f72834f39
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_general.xul
@@ -0,0 +1,179 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible focus event testing">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ function getColorBtn(aBtnObj)
+ {
+ var colorpicker = aBtnObj.colorpicker;
+ var container = colorpicker.firstChild;
+ var btn = container.getChildAt(aBtnObj.btnIndex);
+ return btn;
+ }
+
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTests()
+ {
+ // Test focus events.
+ gQueue = new eventQueue();
+
+ gQueue.push(new synthFocus("textbox",
+ new focusChecker(getNode("textbox").inputField)));
+ gQueue.push(new synthFocus("textbox_multiline",
+ new focusChecker(getNode("textbox_multiline").inputField)));
+ gQueue.push(new synthFocus("scale"));
+ gQueue.push(new synthFocusOnFrame("editabledoc"));
+ gQueue.push(new synthFocus("radioclothes",
+ new focusChecker("radiosweater")));
+ gQueue.push(new synthDownKey("radiosweater",
+ new focusChecker("radiojacket")));
+ gQueue.push(new synthFocus("checkbox"));
+ gQueue.push(new synthFocus("button"));
+ gQueue.push(new synthFocus("checkbutton"));
+ gQueue.push(new synthFocus("radiobutton"));
+
+ // focus menubutton
+ gQueue.push(new synthFocus("menubutton"));
+ // click menubutton, open popup, focus stays on menu button
+ gQueue.push(new synthClick("menubutton", new nofocusChecker()));
+ // select first menu item ("item 1"), focus on menu item
+ gQueue.push(new synthDownKey("menubutton", new focusChecker("mb_item1")));
+ // choose select menu item, focus gets back to menubutton
+ gQueue.push(new synthEnterKey("mb_item1", new focusChecker("menubutton")));
+ // press enter to open popup, focus stays on menubutton
+ gQueue.push(new synthEnterKey("menubutton", new nofocusChecker()));
+ // select second menu item ("item 2"), focus on menu item
+ gQueue.push(new synthUpKey("menubutton", new focusChecker("mb_item2")));
+
+ // clicking on button having associated popup doesn't change a focus
+ gQueue.push(new synthClick("popupbutton", new nofocusChecker()));
+ // select first menu item ("item 1"), focus on menu item
+ gQueue.push(new synthDownKey("popupbutton", new focusChecker("bp_item1")));
+ // choose select menu item, focus gets back to menubutton
+ gQueue.push(new synthEnterKey("bp_item1", new focusChecker("menubutton")));
+ // show popup again for the next test
+ gQueue.push(new synthClick("popupbutton", new nofocusChecker()));
+
+if (!MAC) {
+ // click menubutton of the 'menubutton' button while popup of button open.
+ gQueue.push(new synthClick("mbb", new focusChecker("mbb"), { where: "right" }));
+ // close popup, focus stays on menubutton, fire focus event
+ gQueue.push(new synthEscapeKey("mbb", new focusChecker("mbb")));
+ // click menubutton, open popup, focus stays on menubutton
+ gQueue.push(new synthClick("mbb", new nofocusChecker(), { where: "right" }));
+ // select first menu item ("item 1"), focus on menu item
+ gQueue.push(new synthDownKey("mbb", new focusChecker("mbb_item1")));
+ // choose select menu item, focus gets back to menubutton
+ gQueue.push(new synthEnterKey("mbb_item1", new focusChecker("mbb")));
+ // open popup, focus stays on menubutton
+ gQueue.push(new synthOpenComboboxKey("mbb", new nofocusChecker()));
+ // select second menu item ("item 2"), focus on menu item
+ gQueue.push(new synthUpKey("menubutton", new focusChecker("mbb_item2")));
+ // click on menu item of menubutton menu, focus menubutton
+ gQueue.push(new synthClick("mbb_item2", new focusChecker("mbb")));
+} else {
+ todo(false, "mbb tests time out on OS X, fix bug 746970 and reenable!");
+}
+
+ // focus colorpicker button
+ gQueue.push(new synthFocus("colorpicker"));
+ // click on button, open popup, focus goes to current color button
+ var btnObj = { colorpicker: getAccessible("colorpicker"), btnIndex: 0 };
+ var checker = new focusChecker(getColorBtn, btnObj);
+ gQueue.push(new synthClick("colorpicker", checker));
+ // select sibling color button, focus on it
+ btnObj = { colorpicker: getAccessible("colorpicker"), btnIndex: 1 };
+ var checker = new focusChecker(getColorBtn, btnObj);
+ gQueue.push(new synthRightKey("colorpicker", checker));
+ // choose selected color button, close popup, focus on colorpicker button
+ gQueue.push(new synthEnterKey("colorpicker", new focusChecker("colorpicker")));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=492518"
+ title="xul:slider accessible of xul:scale is accessible illegally">
+ Mozilla Bug 492518
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=552368"
+ title=" fire focus event on document accessible whenever the root or body element is focused">
+ Mozilla Bug 552368
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <textbox id="textbox" value="hello"/>
+ <textbox id="textbox_multiline" multiline="true" value="hello"/>
+ <scale id="scale" min="0" max="9" value="5"/>
+ <iframe id="editabledoc" src="focus.html"/>
+ <radiogroup id="radioclothes">
+ <radio id="radiosweater" label="radiosweater"/>
+ <radio id="radiocap" label="radiocap" disabled="true"/>
+ <radio id="radiojacket" label="radiojacket"/>
+ </radiogroup>
+ <checkbox id="checkbox" label="checkbox"/>
+ <button id="button" label="button"/>
+ <button id="checkbutton" type="checkbox" label="checkbutton"/>
+ <button id="radiobutton" type="radio" group="rbgroup" label="radio1"/>
+
+ <button id="menubutton" type="menu" label="menubutton">
+ <menupopup>
+ <menuitem id="mb_item1" label="item1"/>
+ <menuitem id="mb_item2" label="item2"/>
+ </menupopup>
+ </button>
+ <button id="mbb" type="menu-button" label="menubutton button">
+ <menupopup>
+ <menuitem id="mbb_item1" label="item1"/>
+ <menuitem id="mbb_item2" label="item2"/>
+ </menupopup>
+ </button>
+
+ <colorpicker id="colorpicker" type="button" label="color picker"
+ color="#FFFFFF"/>
+
+ <popupset>
+ <menupopup id="backpopup" position="after_start">
+ <menuitem id="bp_item1" label="Page 1"/>
+ <menuitem id="bp_item2" label="Page 2"/>
+ </menupopup>
+ </popupset>
+ <button id="popupbutton" label="Pop Me Up" popup="backpopup"/>
+
+ <vbox id="eventdump"/>
+ </vbox>
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_focus_listcontrols.xul b/accessible/tests/mochitest/events/test_focus_listcontrols.xul
new file mode 100644
index 000000000..db45048e2
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_listcontrols.xul
@@ -0,0 +1,189 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible focus event testing">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTests()
+ {
+ // Test focus events.
+ gQueue = new eventQueue();
+
+ gQueue.push(new synthFocus("listbox", new focusChecker("lb_item1")));
+ gQueue.push(new synthDownKey("lb_item1", new focusChecker("lb_item2")));
+ gQueue.push(new synthTab("lb_item2", new focusChecker("mslb_item1")));
+ gQueue.push(new synthDownKey("mslb_item1", new focusChecker("mslb_item2"), { shiftKey: true }));
+ gQueue.push(new synthTab("mslb_item2", new focusChecker("emptylistbox")));
+ gQueue.push(new synthFocus("mcolumnlistbox", new focusChecker("mclb_item1")));
+ gQueue.push(new synthDownKey("mclb_item1", new focusChecker("mclb_item2")));
+ gQueue.push(new synthFocus("headerlistbox", new focusChecker("hlb_item1")));
+ gQueue.push(new synthDownKey("hlb_item1", new focusChecker("hlb_item2")));
+
+ gQueue.push(new synthFocus("richlistbox", new focusChecker("rlb_item1")));
+ gQueue.push(new synthDownKey("rlb_item1", new focusChecker("rlb_item2")));
+ gQueue.push(new synthFocus("multiselrichlistbox", new focusChecker("msrlb_item1")));
+ gQueue.push(new synthDownKey("msrlb_item1", new focusChecker("msrlb_item2"), { shiftKey: true }));
+ gQueue.push(new synthFocus("emptyrichlistbox", new focusChecker("emptyrichlistbox")));
+
+ gQueue.push(new synthFocus("menulist"));
+ gQueue.push(new synthClick("menulist", new focusChecker("ml_tangerine")));
+ gQueue.push(new synthDownKey("ml_tangerine", new focusChecker("ml_marmalade")));
+ gQueue.push(new synthEscapeKey("ml_marmalade", new focusChecker("menulist")));
+if (!MAC) {
+ // On Windows, items get selected during navigation.
+ let expectedItem = WIN ? "ml_tangerine" : "ml_marmalade";
+ gQueue.push(new synthDownKey("menulist", new nofocusChecker(expectedItem)));
+ gQueue.push(new synthOpenComboboxKey("menulist", new focusChecker(expectedItem)));
+ gQueue.push(new synthEnterKey(expectedItem, new focusChecker("menulist")));
+} else {
+ todo(false, "Bug 746531 - timeouts of last three menulist tests on OS X");
+}
+
+ var textentry = getAccessible("emenulist").firstChild;
+ gQueue.push(new synthFocus("emenulist", new focusChecker(textentry)));
+ gQueue.push(new synthDownKey(textentry, new nofocusChecker("eml_tangerine")));
+ gQueue.push(new synthUpKey(textentry, new focusChecker("eml_marmalade")));
+ gQueue.push(new synthEnterKey("eml_marmalade", new focusChecker(textentry)));
+ gQueue.push(new synthOpenComboboxKey("emenulist", new focusChecker("eml_marmalade")));
+ gQueue.push(new synthEscapeKey("eml_marmalade", new focusChecker(textentry)));
+
+ // no focus events for unfocused list controls when current item is
+ // changed.
+ gQueue.push(new synthFocus("emptylistbox"));
+
+ gQueue.push(new changeCurrentItem("listbox", "lb_item1"));
+ gQueue.push(new changeCurrentItem("richlistbox", "rlb_item1"));
+if (!MAC) {
+ gQueue.push(new changeCurrentItem("menulist", WIN ? "ml_marmalade" : "ml_tangerine"));
+}
+ gQueue.push(new changeCurrentItem("emenulist", "eml_tangerine"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=433418"
+ title="Accessibles for focused HTML Select elements are not getting focused state">
+ Mozilla Bug 433418
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=474893"
+ title="List controls should fire a focus event on the selected child when tabbing or when the selected child changes while the list is focused">
+ Mozilla Bug 474893
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=552368"
+ title=" fire focus event on document accessible whenever the root or body element is focused">
+ Mozilla Bug 552368
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <listbox id="listbox" rows="3">
+ <listitem id="lb_item1" label="item1"/>
+ <listitem id="lb_item2" label="item1"/>
+ </listbox>
+ <listbox id="multisellistbox" rows="3" seltype="multiple">
+ <listitem id="mslb_item1" label="item1"/>
+ <listitem id="mslb_item2" label="item1"/>
+ </listbox>
+ <listbox id="emptylistbox" rows="3"/>
+ <listbox id="mcolumnlistbox" rows="3">
+ <listcols>
+ <listcol/>
+ <listcol/>
+ </listcols>
+ <listitem id="mclb_item1">
+ <listcell label="George"/>
+ <listcell label="House Painter"/>
+ </listitem>
+ <listitem id="mclb_item2">
+ <listcell label="Mary Ellen"/>
+ <listcell label="Candle Maker"/>
+ </listitem>
+ </listbox>
+ <listbox id="headerlistbox" rows="3">
+ <listhead>
+ <listheader label="Name"/>
+ <listheader label="Occupation"/>
+ </listhead>
+ <listcols>
+ <listcol/>
+ <listcol flex="1"/>
+ </listcols>
+ <listitem id="hlb_item1">
+ <listcell label="George"/>
+ <listcell label="House Painter"/>
+ </listitem>
+ <listitem id="hlb_item2">
+ <listcell label="Mary Ellen"/>
+ <listcell label="Candle Maker"/>
+ </listitem>
+ </listbox>
+
+ <richlistbox id="richlistbox">
+ <richlistitem id="rlb_item1">
+ <description>A XUL Description!</description>
+ </richlistitem>
+ <richlistitem id="rlb_item2">
+ <button label="A XUL Button"/>
+ </richlistitem>
+ </richlistbox>
+ <richlistbox id="multiselrichlistbox" seltype="multiple">
+ <richlistitem id="msrlb_item1">
+ <description>A XUL Description!</description>
+ </richlistitem>
+ <richlistitem id="msrlb_item2">
+ <button label="A XUL Button"/>
+ </richlistitem>
+ </richlistbox>
+ <richlistbox id="emptyrichlistbox" seltype="multiple"/>
+
+ <menulist id="menulist">
+ <menupopup>
+ <menuitem id="ml_tangerine" label="tangerine trees"/>
+ <menuitem id="ml_marmalade" label="marmalade skies"/>
+ </menupopup>
+ </menulist>
+ <menulist id="emenulist" editable="true">
+ <menupopup>
+ <menuitem id="eml_tangerine" label="tangerine trees"/>
+ <menuitem id="eml_marmalade" label="marmalade skies"/>
+ </menupopup>
+ </menulist>
+
+ <vbox id="eventdump"/>
+ </vbox>
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_focus_menu.xul b/accessible/tests/mochitest/events/test_focus_menu.xul
new file mode 100644
index 000000000..f205e8d25
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_menu.xul
@@ -0,0 +1,119 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Menu focus testing">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTests()
+ {
+ // Test focus events.
+ gQueue = new eventQueue();
+
+ if (WIN) {
+ gQueue.push(new toggleTopMenu("fruit", new focusChecker("fruit")));
+ gQueue.push(new synthRightKey("fruit", new focusChecker("vehicle")));
+ gQueue.push(new synthEscapeKey("vehicle", new focusChecker(document)));
+ }
+
+ // mouse move activate items but no focus event until menubar is active
+ gQueue.push(new synthMouseMove("fruit", new nofocusChecker("apple")));
+
+ // mouseover and click on menuitem makes it active before menubar is
+ // active
+ gQueue.push(new synthClick("fruit", new focusChecker("fruit")));
+
+ // mouseover on menuitem when menubar is active
+ gQueue.push(new synthMouseMove("apple", new focusChecker("apple")));
+
+ // keydown on disabled menuitem (disabled items are skipped on linux)
+ if (WIN)
+ gQueue.push(new synthDownKey("apple", new focusChecker("orange")));
+
+ // menu and menuitem are both active
+ // XXX: intermitent failure because two focus events may be coalesced,
+ // think to workaround or fix this issue, when done enable queue invoker
+ // below and remove next two.
+ //gQueue.push(new synthRightKey("apple",
+ // [ new focusChecker("vehicle"),
+ // new focusChecker("cycle")]));
+ gQueue.push(new synthClick("vehicle", new focusChecker("vehicle")));
+ gQueue.push(new synthDownKey("cycle", new focusChecker("cycle")));
+
+ // open submenu
+ gQueue.push(new synthRightKey("cycle", new focusChecker("tricycle")));
+
+ // move to first menu in cycle, DOMMenuItemActive is fired for fruit,
+ // cycle and apple menuitems (bug 685191)
+ todo(false, "focus is fired for 'cycle' menuitem");
+ //gQueue.push(new synthRightKey("vehicle", new focusChecker("apple")));
+
+ // click menuitem to close menu, focus gets back to document
+ gQueue.push(new synthClick("tricycle", new focusChecker(document)));
+
+ //enableLogging("focus,DOMEvents,tree"); // logging for bug708927
+ //gQueue.onFinish = function() { disableLogging(); }
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958"
+ title="Rework accessible focus handling">
+ Mozilla Bug 673958
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <menubar>
+ <menu id="fruit" label="Fruit">
+ <menupopup>
+ <menuitem id="apple" label="Apple"/>
+ <menuitem id="orange" label="Orange" disabled="true"/>
+ </menupopup>
+ </menu>
+ <menu id="vehicle" label="Vehicle">
+ <menupopup>
+ <menu id="cycle" label="cycle">
+ <menupopup>
+ <menuitem id="tricycle" label="tricycle"/>
+ </menupopup>
+ </menu>
+ <menuitem id="car" label="Car" disabled="true"/>
+ </menupopup>
+ </menu>
+ </menubar>
+
+ <vbox id="eventdump"/>
+ </vbox>
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_focus_name.html b/accessible/tests/mochitest/events/test_focus_name.html
new file mode 100644
index 000000000..f0db26427
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_name.html
@@ -0,0 +1,122 @@
+<html>
+
+<head>
+ <title>Accessible name testing on focus</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ /**
+ * Checker for invokers.
+ */
+ function actionChecker(aID, aDescription)
+ {
+ this.__proto__ = new invokerChecker(EVENT_FOCUS, aID);
+
+ this.check = function actionChecker_check(aEvent)
+ {
+ var target = aEvent.accessible;
+ is(target.description, aDescription,
+ "Wrong description for " + prettyName(target));
+ }
+ }
+
+ var gFocusHandler = {
+ handleEvent: function gFocusHandler_handleEvent(aEvent) {
+ var elm = aEvent.target;
+ if (elm.nodeType != nsIDOMNode.ELEMENT_NODE)
+ return;
+
+ gTooltipElm.style.display = "block";
+
+ elm.setAttribute("aria-describedby", "tooltip");
+ }
+ };
+
+ var gBlurHandler = {
+ handleEvent: function gBlurHandler_handleEvent(aEvent) {
+ gTooltipElm.style.display = "none";
+
+ var elm = aEvent.target;
+ if (elm.nodeType == nsIDOMNode.ELEMENT_NODE)
+ elm.removeAttribute("aria-describedby");
+ }
+ };
+
+ /**
+ * Do tests.
+ */
+
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ var gButtonElm = null;
+ var gTextboxElm = null;
+ var gTooltipElm = null;
+
+ function doTests()
+ {
+ gButtonElm = getNode("button");
+ gTextboxElm = getNode("textbox");
+ gTooltipElm = getNode("tooltip");
+
+ gButtonElm.addEventListener("focus", gFocusHandler, false);
+ gButtonElm.addEventListener("blur", gBlurHandler, false);
+ gTextboxElm.addEventListener("focus", gFocusHandler, false);
+ gTextboxElm.addEventListener("blur", gBlurHandler, false);
+
+ // The aria-describedby is changed on DOM focus. Accessible description
+ // should be updated when a11y focus is fired.
+ gQueue = new eventQueue(nsIAccessibleEvent.EVENT_FOCUS);
+ gQueue.onFinish = function()
+ {
+ gButtonElm.removeEventListener("focus", gFocusHandler, false);
+ gButtonElm.removeEventListener("blur", gBlurHandler, false);
+ gTextboxElm.removeEventListener("focus", gFocusHandler, false);
+ gTextboxElm.removeEventListener("blur", gBlurHandler, false);
+ }
+
+ var descr = "It's a tooltip";
+ gQueue.push(new synthFocus("button", new actionChecker("button", descr)));
+ gQueue.push(new synthTab("textbox", new actionChecker("textbox", descr)));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=520709"
+ title="mochitest to ensure name/description are updated on a11y focus if they were changed on DOM focus">
+ Mozilla Bug 520709
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="tooltip" style="display: none" aria-hidden="true">It's a tooltip</div>
+ <button id="button">button</button>
+ <input id="textbox">
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_focus_selects.html b/accessible/tests/mochitest/events/test_focus_selects.html
new file mode 100644
index 000000000..ef742c4b2
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_selects.html
@@ -0,0 +1,118 @@
+<html>
+
+<head>
+ <title>Accessible focus testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTests()
+ {
+ // Bug 746534 - File causes crash or hang on OS X
+ if (MAC) {
+ todo(false, "Bug 746534 - test file causes crash or hang on OS X");
+ SimpleTest.finish();
+ return;
+ }
+
+ gQueue = new eventQueue();
+
+ // first item is focused until there's selection
+ gQueue.push(new synthFocus("list", new focusChecker("orange")));
+
+ // item is selected and stays focused
+ gQueue.push(new synthDownKey("list", new nofocusChecker()));
+
+ // last selected item is focused
+ gQueue.push(new synthDownKey("list", new focusChecker("apple"), { shiftKey: true }));
+
+ // no focus event if nothing is changed
+ gQueue.push(new synthDownKey("list", new nofocusChecker("apple")));
+
+ // current item is focused
+ gQueue.push(new synthUpKey("list", new focusChecker("orange"), { ctrlKey: true }));
+
+ // focus on empty list (no items to be focused)
+ gQueue.push(new synthTab("list", new focusChecker("emptylist")));
+
+ // current item is focused
+ gQueue.push(new synthShiftTab("emptylist", new focusChecker("orange")));
+
+ // collapsed combobox keeps a focus
+ gQueue.push(new synthFocus("combobox", new focusChecker("combobox")));
+ gQueue.push(new synthDownKey("combobox", new nofocusChecker("combobox")));
+
+ // selected item is focused for expanded combobox
+ gQueue.push(new synthOpenComboboxKey("combobox", new focusChecker("cb_apple")));
+ gQueue.push(new synthUpKey("combobox", new focusChecker("cb_orange")));
+
+ // collapsed combobx keeps a focus
+ gQueue.push(new synthEscapeKey("combobox", new focusChecker("combobox")));
+
+ // no focus events for unfocused list controls when current item is
+ // changed
+ gQueue.push(new synthFocus("emptylist"));
+
+ gQueue.push(new changeCurrentItem("list", "orange"));
+ gQueue.push(new changeCurrentItem("combobox", "cb_apple"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=433418"
+ title="Accessibles for focused HTML Select elements are not getting focused state">
+ Mozilla Bug 433418
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=474893"
+ title="List controls should fire a focus event on the selected child when tabbing or when the selected child changes while the list is focused">
+ Mozilla Bug 474893
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <select id="list" size="5" multiple="">
+ <option id="orange">Orange</option>
+ <option id="apple">Apple</option>
+ </select>
+
+ <select id="emptylist" size="5"></select>
+
+ <select id="combobox">
+ <option id="cb_orange">Orange</option>
+ <option id="cb_apple">Apple</option>
+ </select>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_focus_tabbox.xul b/accessible/tests/mochitest/events/test_focus_tabbox.xul
new file mode 100644
index 000000000..c51546405
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_tabbox.xul
@@ -0,0 +1,103 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Tabbox focus testing">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTests()
+ {
+ if (MAC) {
+ todo(false, "Tests disabled because of imminent failure.");
+ SimpleTest.finish();
+ return;
+ }
+
+ // Test focus events.
+ gQueue = new eventQueue();
+
+ var textbox = getNode("textbox").inputField;
+ gQueue.push(new synthClick("tab1", new focusChecker("tab1")));
+ gQueue.push(new synthTab("tab1", new focusChecker("checkbox1")));
+ gQueue.push(new synthKey("tab1", "VK_TAB", { ctrlKey: true },
+ new focusChecker(textbox)));
+ gQueue.push(new synthKey("tab2", "VK_TAB", { ctrlKey: true },
+ new focusChecker("tab3")));
+ gQueue.push(new synthKey("tab3", "VK_TAB", { ctrlKey: true },
+ new focusChecker("tab1")));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=370396"
+ title="Control+Tab to an empty tab panel in a tabbox causes focus to leave the tabbox">
+ Mozilla Bug 370396
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <tabbox>
+ <tabs>
+ <tab id="tab1" label="Tab1" selected="true"/>
+ <tab id="tab2" label="Tab2" />
+ <tab id="tab3" label="Tab3" />
+ </tabs>
+ <tabpanels>
+ <tabpanel orient="vertical">
+ <groupbox orient="vertical">
+ <checkbox id="checkbox1" label="Monday" width="75"/>
+ <checkbox label="Tuesday" width="75"/>
+ <checkbox label="Wednesday" width="75"/>
+ <checkbox label="Thursday" width="75"/>
+ <checkbox label="Friday" width="75"/>
+ <checkbox label="Saturday" width="75"/>
+ <checkbox label="Sunday" width="75"/>
+ </groupbox>
+
+ <spacer style="height: 10px" />
+ <label value="Label After checkboxes" />
+ </tabpanel>
+ <tabpanel orient="vertical">
+ <textbox id="textbox" />
+ </tabpanel>
+ <tabpanel orient="vertical">
+ <description>Tab 3 content</description>
+ </tabpanel>
+ </tabpanels>
+ </tabbox>
+
+ <vbox id="eventdump"/>
+ </vbox>
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_focus_tree.xul b/accessible/tests/mochitest/events/test_focus_tree.xul
new file mode 100644
index 000000000..995b08e25
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_focus_tree.xul
@@ -0,0 +1,122 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL tree focus testing">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/MochiKit/packed.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function focusTree(aTreeID)
+ {
+ var checker = new focusChecker(getFirstTreeItem, aTreeID);
+ this.__proto__ = new synthFocus(aTreeID, [ checker ]);
+ }
+
+ function moveToNextItem(aTreeID)
+ {
+ var checker = new focusChecker(getSecondTreeItem, aTreeID);
+ this.__proto__ = new synthDownKey(aTreeID, [ checker ]);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Helpers
+
+ function getTreeItemAt(aTreeID, aIdx)
+ { return getAccessible(aTreeID).getChildAt(aIdx + 1); }
+
+ function getFirstTreeItem(aTreeID)
+ { return getTreeItemAt(aTreeID, 0); }
+
+ function getSecondTreeItem(aTreeID)
+ { return getTreeItemAt(aTreeID, 1); }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ var gQueue = null;
+
+ //gA11yEventDumpID = "debug"; // debugging
+ //gA11yEventDumpToConsole = true; // debugging
+
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new focusTree("tree"));
+ gQueue.push(new moveToNextItem("tree"));
+ gQueue.push(new synthFocus("emptytree"));
+
+ // no focus event for changed current item for unfocused tree
+ gQueue.push(new changeCurrentItem("tree", 0));
+
+ gQueue.invoke();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yXULTreeLoadEvent(doTest, "tree", new nsTableTreeView(5));
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=386821"
+ title="Need better solution for firing delayed event against xul tree">
+ Mozilla Bug 386821
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=406308"
+ title="Don't fire accessible focus events if widget is not actually in focus, confuses screen readers">
+ Mozilla Bug 406308
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="debug"/>
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col1" flex="1" primary="true" label="column"/>
+ <treecol id="col2" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren id="treechildren"/>
+ </tree>
+ <tree id="emptytree" flex="1">
+ <treecols>
+ <treecol id="emptytree_col1" flex="1" primary="true" label="column"/>
+ <treecol id="emptytree_col2" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren id="emptytree_treechildren"/>
+ </tree>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/events/test_fromUserInput.html b/accessible/tests/mochitest/events/test_fromUserInput.html
new file mode 100644
index 000000000..1cfeedf0b
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_fromUserInput.html
@@ -0,0 +1,127 @@
+<html>
+
+<head>
+ <title>Testing of isFromUserInput in text events</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ /**
+ * Remove text data from HTML input.
+ */
+ function removeTextFromInput(aID, aStart, aEnd, aText, aFromUser)
+ {
+ this.DOMNode = getNode(aID);
+
+ this.eventSeq = [
+ new textChangeChecker(aID, aStart, aEnd, aText, false, aFromUser)
+ ];
+
+ this.invoke = function removeTextFromInput_invoke()
+ {
+ const nsIDOMNSEditableElement =
+ Components.interfaces.nsIDOMNSEditableElement;
+
+ this.DOMNode.focus();
+ this.DOMNode.setSelectionRange(aStart, aEnd);
+
+ synthesizeKey("VK_DELETE", {});
+ }
+
+ this.getID = function removeTextFromInput_getID()
+ {
+ return "Remove text from " + aStart + " to " + aEnd + " for " +
+ prettyName(aID);
+ }
+ }
+
+ /**
+ * Remove text data from text node.
+ */
+ function removeTextFromContentEditable(aID, aStart, aEnd, aText, aFromUser)
+ {
+ this.DOMNode = getNode(aID);
+
+ this.eventSeq = [
+ new textChangeChecker(aID, aStart, aEnd, aText, false, aFromUser)
+ ];
+
+ this.invoke = function removeTextFromContentEditable_invoke()
+ {
+ const nsIDOMNSEditableElement =
+ Components.interfaces.nsIDOMNSEditableElement;
+
+ this.DOMNode.focus();
+ this.textNode = getNode(aID).firstChild;
+ var selection = window.getSelection();
+ var range = document.createRange();
+ range.setStart(this.textNode, aStart);
+ range.setEnd(this.textNode, aEnd);
+ selection.addRange(range);
+
+ synthesizeKey("VK_DELETE", {});
+ }
+
+ this.getID = function removeTextFromContentEditable_getID()
+ {
+ return "Remove text from " + aStart + " to " + aEnd + " for " +
+ prettyName(aID);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Do tests
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+
+ var gQueue = null;
+
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ // Focused editable text node
+ gQueue.push(new removeTextFromContentEditable("div", 0, 3, "hel", true));
+
+ // Focused editable HTML input
+ gQueue.push(new removeTextFromInput("input", 1, 2, "n", true));
+
+ gQueue.invoke(); // Will call SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+
+ </script>
+</head>
+
+
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=686909"
+ title="isFromUserInput flag on accessible text change events not correct">
+ Mozilla Bug 686909
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ <div id="eventdump"></div>
+
+ <div id="div" contentEditable="true">hello</div>
+ <input id="input" value="input">
+
+</body>
+
+</html>
diff --git a/accessible/tests/mochitest/events/test_label.xul b/accessible/tests/mochitest/events/test_label.xul
new file mode 100644
index 000000000..e82763a7c
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_label.xul
@@ -0,0 +1,177 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Tests: accessible XUL label/description events">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ const kRecreated = 0;
+ const kTextRemoved = 1;
+ const kTextChanged = 2;
+
+ const kNoValue = 0;
+
+ /**
+ * Set/remove @value attribute.
+ */
+ function setValue(aID, aValue, aResult, aOldValue)
+ {
+ this.labelNode = getNode(aID);
+
+ this.eventSeq = [];
+
+ switch (aResult) {
+ case kRecreated:
+ this.eventSeq.push(new invokerChecker(EVENT_HIDE, this.labelNode));
+ this.eventSeq.push(new invokerChecker(EVENT_SHOW, this.labelNode));
+ break;
+ case kTextRemoved:
+ this.eventSeq.push(
+ new textChangeChecker(this.labelNode, 0, aOldValue.length,
+ aOldValue, false));
+ break;
+ case kTextChanged:
+ this.eventSeq.push(
+ new textChangeChecker(this.labelNode, 0, aOldValue.length,
+ aOldValue, false));
+ this.eventSeq.push(
+ new textChangeChecker(this.labelNode, 0, aValue.length,
+ aValue, true));
+ break;
+ }
+
+ this.invoke = function setValue_invoke()
+ {
+ if (aValue === kNoValue)
+ this.labelNode.removeAttribute("value");
+ else
+ this.labelNode.setAttribute("value", aValue);
+ }
+
+ this.finalCheck = function setValue_finalCheck()
+ {
+ var tree =
+ { LABEL: [
+ { TEXT_LEAF: [ ] }
+ ] };
+ testAccessibleTree(aID, tree);
+ }
+
+ this.getID = function setValue_getID()
+ {
+ return "set @value='" + aValue + "' for label " + prettyName(aID);
+ }
+ }
+
+ /**
+ * Change @crop attribute.
+ */
+ function setCrop(aID, aCropValue, aRemovedText, aInsertedText)
+ {
+ this.labelNode = getNode(aID);
+ this.width = this.labelNode.boxObject.width;
+ this.charWidth = this.width / this.labelNode.value.length;
+
+ this.eventSeq = [
+ new textChangeChecker(this.labelNode, 0, -1, aRemovedText, false),
+ new textChangeChecker(this.labelNode, 0, -1, aInsertedText, true)
+ ];
+
+ this.invoke = function setCrop_invoke()
+ {
+ if (!this.labelNode.hasAttribute("crop"))
+ this.labelNode.width = Math.floor(this.width - 2 * this.charWidth);
+
+ this.labelNode.setAttribute("crop", aCropValue);
+ }
+
+ this.getID = function setCrop_finalCheck()
+ {
+ return "set crop " + aCropValue;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new setValue("label", "shiroka strana", kRecreated));
+ gQueue.push(new setValue("label", "?<>!+_", kTextChanged, "shiroka strana"));
+ gQueue.push(new setValue("label", "", kTextRemoved, "?<>!+_"));
+ gQueue.push(new setValue("label", kNoValue, kRecreated));
+
+ gQueue.push(new setValue("descr", "hello world", kRecreated));
+ gQueue.push(new setValue("descr", "si_ya", kTextChanged, "hello world"));
+ gQueue.push(new setValue("descr", "", kTextRemoved, "si_ya"));
+ gQueue.push(new setValue("descr", kNoValue, kRecreated));
+
+ if (MAC) {
+ // "valuetocro" -> "…etocro"
+ gQueue.push(new setCrop("croplabel", "left", "valu", "…"));
+ // "…etocro", "val…cro"
+ gQueue.push(new setCrop("croplabel", "center", "…eto", "val…"));
+ } else {
+ // "valuetocro" -> "…uetocro"
+ gQueue.push(new setCrop("croplabel", "left", "val", "…"));
+ // "…uetocro" -> "valu…cro"
+ gQueue.push(new setCrop("croplabel", "center", "…ueto", "valu…"));
+ }
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=396166"
+ title="xul:label@value accessible should implement nsIAccessibleText">
+ Bug 396166
+ </a>
+ <br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <label id="label">hello</label>
+ <description id="descr">hello</description>
+
+ <hbox>
+ <label id="croplabel" value="valuetocro"
+ style="font-family: monospace;"/>
+ </hbox>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/events/test_menu.xul b/accessible/tests/mochitest/events/test_menu.xul
new file mode 100644
index 000000000..bae36fb71
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_menu.xul
@@ -0,0 +1,202 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible menu events testing for XUL menu">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ function openFileMenu()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_MENU_START, getNode("menubar")),
+ new invokerChecker(EVENT_MENUPOPUP_START, getNode("menupopup-file"))
+ // new invokerChecker(EVENT_FOCUS, getNode("menuitem-newtab")) intermitent failure
+ ];
+
+ this.invoke = function openFileMenu_invoke()
+ {
+ synthesizeKey("F", { altKey: true, shiftKey: true });
+ }
+
+ this.getID = function openFileMenu_getID()
+ {
+ return "open file menu by alt+F press";
+ }
+ }
+
+ function openEditMenu()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_MENUPOPUP_END, getNode("menupopup-file")),
+ new invokerChecker(EVENT_MENUPOPUP_START, getNode("menupopup-edit"))
+ // new invokerChecker(EVENT_FOCUS, getNode("menuitem-undo")) intermitent failure
+ ];
+
+ this.invoke = function openEditMenu_invoke()
+ {
+ synthesizeKey("VK_RIGHT", { });
+ }
+
+ this.getID = function openEditMenu_getID()
+ {
+ return "open edit menu by lef arrow press";
+ }
+ }
+
+ function closeEditMenu()
+ {
+ this.eventSeq = [
+ //new invokerChecker(EVENT_FOCUS, document), intermitent failure
+ new invokerChecker(EVENT_MENUPOPUP_END, getNode("menupopup-edit")),
+ new invokerChecker(EVENT_MENU_END, getNode("menubar"))
+ ];
+
+ this.invoke = function closeEditMenu_invoke()
+ {
+ synthesizeKey("VK_ESCAPE", { });
+ }
+
+ this.getID = function closeEditMenu_getID()
+ {
+ return "close edit menu, leave menubar";
+ }
+ }
+
+ function focusFileMenu()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_MENU_START, getNode("menubar"))
+ // new invokerChecker(EVENT_FOCUS, getNode("menuitem-file")) //intermitent failure
+ ];
+
+ this.invoke = function focusFileMenu_invoke()
+ {
+ synthesizeKey("VK_ALT", { });
+ }
+
+ this.getID = function focusFileMenu_getID()
+ {
+ return "activate menubar, focus file menu (atl press)";
+ }
+ }
+
+ function focusEditMenu()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, getNode("menuitem-edit"))
+ ];
+
+ this.invoke = function focusEditMenu_invoke()
+ {
+ synthesizeKey("VK_RIGHT", { });
+ }
+
+ this.getID = function focusEditMenu_getID()
+ {
+ return "focus edit menu";
+ }
+ }
+
+ function leaveMenubar()
+ {
+ this.eventSeq = [
+ //new invokerChecker(EVENT_FOCUS, document), intermitent failure
+ new invokerChecker(EVENT_MENU_END, getNode("menubar"))
+ ];
+
+ this.invoke = function leaveMenubar_invoke()
+ {
+ synthesizeKey("VK_ESCAPE", { });
+ }
+
+ this.getID = function leaveMenubar_getID()
+ {
+ return "leave menubar";
+ }
+ }
+
+ /**
+ * Do tests.
+ */
+
+ //gA11yEventDumpID = "eventdump";
+ //gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTests()
+ {
+ if (!WIN) {
+ todo(false, "Enable this test on other platforms.");
+ SimpleTest.finish();
+ return;
+ }
+
+ todo(false,
+ "Fix intermitent failures. Focus may randomly occur before or after menupopup events!");
+
+ gQueue = new eventQueue();
+
+ gQueue.push(new openFileMenu());
+ gQueue.push(new openEditMenu());
+ gQueue.push(new closeEditMenu());
+
+ // Alt key is used to active menubar and focus menu item on Windows,
+ // other platforms requires setting a ui.key.menuAccessKeyFocuses
+ // preference.
+ if (WIN || LINUX) {
+ gQueue.push(new focusFileMenu());
+ gQueue.push(new focusEditMenu());
+ gQueue.push(new leaveMenubar());
+ }
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=615189"
+ title="Clean up FireAccessibleFocusEvent">
+ Mozilla Bug 615189
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <menubar id="menubar">
+ <menu id="menuitem-file" label="File" accesskey="F">
+ <menupopup id="menupopup-file">
+ <menuitem id="menuitem-newtab" label="New Tab"/>
+ </menupopup>
+ </menu>
+ <menu id="menuitem-edit" label="Edit" accesskey="E">
+ <menupopup id="menupopup-edit">
+ <menuitem id="menuitem-undo" label="Undo"/>
+ </menupopup>
+ </menu>
+ </menubar>
+
+ <vbox id="eventdump" role="log"/>
+ </vbox>
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_mutation.html b/accessible/tests/mochitest/events/test_mutation.html
new file mode 100644
index 000000000..232a09727
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_mutation.html
@@ -0,0 +1,632 @@
+<html>
+
+<head>
+ <title>Accessible mutation events testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <style>
+ div.displayNone a { display:none; }
+ div.visibilityHidden a { visibility:hidden; }
+</style>
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ /**
+ * Invokers.
+ */
+ var kNoEvents = 0;
+
+ var kShowEvent = 1;
+ var kHideEvent = 2;
+ var kReorderEvent = 4;
+ var kShowEvents = kShowEvent | kReorderEvent;
+ var kHideEvents = kHideEvent | kReorderEvent;
+ var kHideAndShowEvents = kHideEvents | kShowEvent;
+
+ /**
+ * Base class to test mutation a11y events.
+ *
+ * @param aNodeOrID [in] node invoker's action is executed for
+ * @param aEventTypes [in] events to register (see constants above)
+ * @param aDoNotExpectEvents [in] boolean indicates if events are expected
+ */
+ function mutateA11yTree(aNodeOrID, aEventTypes, aDoNotExpectEvents)
+ {
+ // Interface
+ this.DOMNode = getNode(aNodeOrID);
+ this.doNotExpectEvents = aDoNotExpectEvents;
+ this.eventSeq = [];
+ this.unexpectedEventSeq = [];
+
+ /**
+ * Change default target (aNodeOrID) registered for the given event type.
+ */
+ this.setTarget = function mutateA11yTree_setTarget(aEventType, aTarget)
+ {
+ var type = this.getA11yEventType(aEventType);
+ for (var idx = 0; idx < this.getEventSeq().length; idx++) {
+ if (this.getEventSeq()[idx].type == type) {
+ this.getEventSeq()[idx].target = aTarget;
+ return idx;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Replace the default target currently registered for a given event type
+ * with the nodes in the targets array.
+ */
+ this.setTargets = function mutateA11yTree_setTargets(aEventType, aTargets) {
+ var targetIdx = this.setTarget(aEventType, aTargets[0]);
+
+ var type = this.getA11yEventType(aEventType);
+ for (var i = 1; i < aTargets.length; i++) {
+ var checker = new invokerChecker(type, aTargets[i]);
+ this.getEventSeq().splice(++targetIdx, 0, checker);
+ }
+ }
+
+ // Implementation
+ this.getA11yEventType = function mutateA11yTree_getA11yEventType(aEventType)
+ {
+ if (aEventType == kReorderEvent)
+ return nsIAccessibleEvent.EVENT_REORDER;
+
+ if (aEventType == kHideEvent)
+ return nsIAccessibleEvent.EVENT_HIDE;
+
+ if (aEventType == kShowEvent)
+ return nsIAccessibleEvent.EVENT_SHOW;
+ }
+
+ this.getEventSeq = function mutateA11yTree_getEventSeq()
+ {
+ return this.doNotExpectEvents ? this.unexpectedEventSeq : this.eventSeq;
+ }
+
+ if (aEventTypes & kHideEvent) {
+ var checker = new invokerChecker(this.getA11yEventType(kHideEvent),
+ this.DOMNode);
+ this.getEventSeq().push(checker);
+ }
+
+ if (aEventTypes & kShowEvent) {
+ var checker = new invokerChecker(this.getA11yEventType(kShowEvent),
+ this.DOMNode);
+ this.getEventSeq().push(checker);
+ }
+
+ if (aEventTypes & kReorderEvent) {
+ var checker = new invokerChecker(this.getA11yEventType(kReorderEvent),
+ this.DOMNode.parentNode);
+ this.getEventSeq().push(checker);
+ }
+ }
+
+ /**
+ * Change CSS style for the given node.
+ */
+ function changeStyle(aNodeOrID, aProp, aValue, aEventTypes)
+ {
+ this.__proto__ = new mutateA11yTree(aNodeOrID, aEventTypes, false);
+
+ this.invoke = function changeStyle_invoke()
+ {
+ this.DOMNode.style[aProp] = aValue;
+ }
+
+ this.getID = function changeStyle_getID()
+ {
+ return aNodeOrID + " change style " + aProp + " on value " + aValue;
+ }
+ }
+
+ /**
+ * Change class name for the given node.
+ */
+ function changeClass(aParentNodeOrID, aNodeOrID, aClassName, aEventTypes)
+ {
+ this.__proto__ = new mutateA11yTree(aNodeOrID, aEventTypes, false);
+
+ this.invoke = function changeClass_invoke()
+ {
+ this.parentDOMNode.className = aClassName;
+ }
+
+ this.getID = function changeClass_getID()
+ {
+ return aNodeOrID + " change class " + aClassName;
+ }
+
+ this.parentDOMNode = getNode(aParentNodeOrID);
+ }
+
+ /**
+ * Clone the node and append it to its parent.
+ */
+ function cloneAndAppendToDOM(aNodeOrID, aEventTypes,
+ aTargetsFunc, aReorderTargetFunc)
+ {
+ var eventTypes = aEventTypes || kShowEvents;
+ var doNotExpectEvents = (aEventTypes == kNoEvents);
+
+ this.__proto__ = new mutateA11yTree(aNodeOrID, eventTypes,
+ doNotExpectEvents);
+
+ this.invoke = function cloneAndAppendToDOM_invoke()
+ {
+ var newElm = this.DOMNode.cloneNode(true);
+ newElm.removeAttribute('id');
+
+ var targets = aTargetsFunc ?
+ aTargetsFunc.call(null, newElm) : [newElm];
+ this.setTargets(kShowEvent, targets);
+
+ if (aReorderTargetFunc) {
+ var reorderTarget = aReorderTargetFunc.call(null, this.DOMNode);
+ this.setTarget(kReorderEvent, reorderTarget);
+ }
+
+ this.DOMNode.parentNode.appendChild(newElm);
+ }
+
+ this.getID = function cloneAndAppendToDOM_getID()
+ {
+ return aNodeOrID + " clone and append to DOM.";
+ }
+ }
+
+ /**
+ * Removes the node from DOM.
+ */
+ function removeFromDOM(aNodeOrID, aEventTypes,
+ aTargetsFunc, aReorderTargetFunc)
+ {
+ var eventTypes = aEventTypes || kHideEvents;
+ var doNotExpectEvents = (aEventTypes == kNoEvents);
+
+ this.__proto__ = new mutateA11yTree(aNodeOrID, eventTypes,
+ doNotExpectEvents);
+
+ this.invoke = function removeFromDOM_invoke()
+ {
+ this.DOMNode.parentNode.removeChild(this.DOMNode);
+ }
+
+ this.getID = function removeFromDOM_getID()
+ {
+ return prettyName(aNodeOrID) + " remove from DOM.";
+ }
+
+ if (aTargetsFunc && (eventTypes & kHideEvent))
+ this.setTargets(kHideEvent, aTargetsFunc.call(null, this.DOMNode));
+
+ if (aReorderTargetFunc && (eventTypes & kReorderEvent))
+ this.setTarget(kReorderEvent,
+ aReorderTargetFunc.call(null, this.DOMNode));
+ }
+
+ /**
+ * Clone the node and replace the original node by cloned one.
+ */
+ function cloneAndReplaceInDOM(aNodeOrID)
+ {
+ this.__proto__ = new mutateA11yTree(aNodeOrID, kHideAndShowEvents,
+ false);
+
+ this.invoke = function cloneAndReplaceInDOM_invoke()
+ {
+ this.DOMNode.parentNode.replaceChild(this.newElm, this.DOMNode);
+ }
+
+ this.getID = function cloneAndReplaceInDOM_getID()
+ {
+ return aNodeOrID + " clone and replace in DOM.";
+ }
+
+ this.newElm = this.DOMNode.cloneNode(true);
+ this.newElm.removeAttribute('id');
+ this.setTarget(kShowEvent, this.newElm);
+ }
+
+ /**
+ * Trigger content insertion (flush layout), removal and insertion of
+ * the same element for the same parent.
+ */
+ function test1(aContainerID)
+ {
+ this.divNode = document.createElement("div");
+ this.divNode.setAttribute("id", "div-test1");
+ this.containerNode = getNode(aContainerID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, this.divNode),
+ new invokerChecker(EVENT_REORDER, this.containerNode)
+ ];
+
+ this.invoke = function test1_invoke()
+ {
+ this.containerNode.appendChild(this.divNode);
+ getComputedStyle(this.divNode, "").color;
+ this.containerNode.removeChild(this.divNode);
+ this.containerNode.appendChild(this.divNode);
+ }
+
+ this.getID = function test1_getID()
+ {
+ return "fuzzy test #1: content insertion (flush layout), removal and" +
+ "reinsertion";
+ }
+ }
+
+ /**
+ * Trigger content insertion (flush layout), removal and insertion of
+ * the same element for the different parents.
+ */
+ function test2(aContainerID, aTmpContainerID)
+ {
+ this.divNode = document.createElement("div");
+ this.divNode.setAttribute("id", "div-test2");
+ this.containerNode = getNode(aContainerID);
+ this.tmpContainerNode = getNode(aTmpContainerID);
+ this.container = getAccessible(this.containerNode);
+ this.tmpContainer = getAccessible(this.tmpContainerNode);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, this.divNode),
+ new invokerChecker(EVENT_REORDER, this.containerNode)
+ ];
+
+ this.unexpectedEventSeq = [
+ new invokerChecker(EVENT_REORDER, this.tmpContainerNode)
+ ];
+
+ this.invoke = function test2_invoke()
+ {
+ this.tmpContainerNode.appendChild(this.divNode);
+ getComputedStyle(this.divNode, "").color;
+ this.tmpContainerNode.removeChild(this.divNode);
+ this.containerNode.appendChild(this.divNode);
+ }
+
+ this.getID = function test2_getID()
+ {
+ return "fuzzy test #2: content insertion (flush layout), removal and" +
+ "reinsertion under another container";
+ }
+ }
+
+ /**
+ * Content insertion (flush layout) and then removal (nothing was changed).
+ */
+ function test3(aContainerID)
+ {
+ this.divNode = document.createElement("div");
+ this.divNode.setAttribute("id", "div-test3");
+ this.containerNode = getNode(aContainerID);
+
+ this.unexpectedEventSeq = [
+ new invokerChecker(EVENT_SHOW, this.divNode),
+ new invokerChecker(EVENT_HIDE, this.divNode),
+ new invokerChecker(EVENT_REORDER, this.containerNode)
+ ];
+
+ this.invoke = function test3_invoke()
+ {
+ this.containerNode.appendChild(this.divNode);
+ getComputedStyle(this.divNode, "").color;
+ this.containerNode.removeChild(this.divNode);
+ }
+
+ this.getID = function test3_getID()
+ {
+ return "fuzzy test #3: content insertion (flush layout) and removal";
+ }
+ }
+
+ function insertReferredElm(aContainerID)
+ {
+ this.containerNode = getNode(aContainerID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, function(aNode) { return aNode.firstChild; }, this.containerNode),
+ new invokerChecker(EVENT_SHOW, function(aNode) { return aNode.lastChild; }, this.containerNode),
+ new invokerChecker(EVENT_REORDER, this.containerNode)
+ ];
+
+ this.invoke = function insertReferredElm_invoke()
+ {
+ this.containerNode.innerHTML =
+ "<span id='insertReferredElms_span'></span><input aria-labelledby='insertReferredElms_span'>";
+ }
+
+ this.getID = function insertReferredElm_getID()
+ {
+ return "insert inaccessible element and then insert referring element to make it accessible";
+ }
+ }
+
+ function showHiddenParentOfVisibleChild()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode("c4_child")),
+ new invokerChecker(EVENT_SHOW, getNode("c4_middle")),
+ new invokerChecker(EVENT_REORDER, getNode("c4"))
+ ];
+
+ this.invoke = function showHiddenParentOfVisibleChild_invoke()
+ {
+ getNode("c4_middle").style.visibility = 'visible';
+ }
+
+ this.getID = function showHiddenParentOfVisibleChild_getID()
+ {
+ return "show hidden parent of visible child";
+ }
+ }
+
+ function hideNDestroyDoc()
+ {
+ this.txt = null;
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, function() { return this.txt; }.bind(this))
+ ];
+
+ this.invoke = function hideNDestroyDoc_invoke()
+ {
+ this.txt = getAccessible('c5').firstChild.firstChild;
+ this.txt.DOMNode.parentNode.removeChild(this.txt.DOMNode);
+ }
+
+ this.check = function hideNDestroyDoc_check()
+ {
+ getNode('c5').parentNode.removeChild(getNode('c5'));
+ }
+
+ this.getID = function hideNDestroyDoc_getID()
+ {
+ return "remove text node and destroy a document on hide event";
+ }
+ }
+
+ function hideHideNDestroyDoc()
+ {
+ this.target = null;
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, function() { return this.target; }.bind(this))
+ ];
+
+ this.invoke = function hideHideNDestroyDoc_invoke()
+ {
+ var doc = getAccessible('c6').firstChild;
+ var l1 = doc.firstChild;
+ this.target = l1.firstChild;
+ var l2 = doc.lastChild;
+ l1.DOMNode.removeChild(l1.DOMNode.firstChild);
+ l2.DOMNode.removeChild(l2.DOMNode.firstChild);
+ }
+
+ this.check = function hideHideNDestroyDoc_check()
+ {
+ getNode('c6').parentNode.removeChild(getNode('c6'));
+ }
+
+ this.getID = function hideHideNDestroyDoc_getID()
+ {
+ return "remove text nodes (2 events in the queue) and destroy a document on first hide event";
+ }
+ }
+
+ /**
+ * Target getters.
+ */
+ function getFirstChild(aNode)
+ {
+ return [aNode.firstChild];
+ }
+ function getLastChild(aNode)
+ {
+ return [aNode.lastChild];
+ }
+
+ function getNEnsureFirstChild(aNode)
+ {
+ var node = aNode.firstChild;
+ getAccessible(node);
+ return [node];
+ }
+
+ function getNEnsureChildren(aNode)
+ {
+ var children = [];
+ var node = aNode.firstChild;
+ do {
+ children.push(node);
+ getAccessible(node);
+ node = node.nextSibling;
+ } while (node);
+
+ return children;
+ }
+
+ function getParent(aNode)
+ {
+ return aNode.parentNode;
+ }
+
+ //gA11yEventDumpToConsole = true; // debug stuff
+ //enableLogging("events,verbose");
+
+ /**
+ * Do tests.
+ */
+ var gQueue = null;
+
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ // Show/hide events by changing of display style of accessible DOM node
+ // from 'inline' to 'none', 'none' to 'inline'.
+ var id = "link1";
+ getAccessible(id); // ensure accessible is created
+ gQueue.push(new changeStyle(id, "display", "none", kHideEvents));
+ gQueue.push(new changeStyle(id, "display", "inline", kShowEvents));
+
+ // Show/hide events by changing of visibility style of accessible DOM node
+ // from 'visible' to 'hidden', 'hidden' to 'visible'.
+ var id = "link2";
+ getAccessible(id);
+ gQueue.push(new changeStyle(id, "visibility", "hidden", kHideEvents));
+ gQueue.push(new changeStyle(id, "visibility", "visible", kShowEvents));
+
+ // Show/hide events by changing of display style of accessible DOM node
+ // from 'inline' to 'block', 'block' to 'inline'.
+ var id = "link3";
+ getAccessible(id); // ensure accessible is created
+ gQueue.push(new changeStyle(id, "display", "block", kHideAndShowEvents));
+ gQueue.push(new changeStyle(id, "display", "inline", kHideAndShowEvents));
+
+ // Show/hide events by changing of visibility style of accessible DOM node
+ // from 'collapse' to 'visible', 'visible' to 'collapse'.
+ var id = "link4";
+ gQueue.push(new changeStyle(id, "visibility", "visible", kShowEvents));
+ gQueue.push(new changeStyle(id, "visibility", "collapse", kHideEvents));
+
+ // Show/hide events by adding new accessible DOM node and removing old one.
+ var id = "link5";
+ gQueue.push(new cloneAndAppendToDOM(id));
+ gQueue.push(new removeFromDOM(id));
+
+ // No show/hide events by adding new not accessible DOM node and removing
+ // old one, no reorder event for their parent.
+ var id = "child1";
+ gQueue.push(new cloneAndAppendToDOM(id, kNoEvents));
+ gQueue.push(new removeFromDOM(id, kNoEvents));
+
+ // Show/hide events by adding new accessible DOM node and removing
+ // old one, there is reorder event for their parent.
+ var id = "child2";
+ gQueue.push(new cloneAndAppendToDOM(id));
+ gQueue.push(new removeFromDOM(id));
+
+ // Show/hide events by adding new DOM node containing accessible DOM and
+ // removing old one, there is reorder event for their parent.
+ var id = "child3";
+ gQueue.push(new cloneAndAppendToDOM(id, kShowEvents, getFirstChild,
+ getParent));
+
+ // Hide event for accessible child of unaccessible removed DOM node and
+ // reorder event for its parent.
+ gQueue.push(new removeFromDOM(id, kHideEvents,
+ getNEnsureFirstChild, getParent));
+
+ // Hide events for accessible children of unaccessible removed DOM node
+ // and reorder event for its parent.
+ gQueue.push(new removeFromDOM("child4", kHideEvents,
+ getNEnsureChildren, getParent));
+
+ // Show/hide events by creating new accessible DOM node and replacing
+ // old one.
+ getAccessible("link6"); // ensure accessible is created
+ gQueue.push(new cloneAndReplaceInDOM("link6"));
+
+ // Show/hide events by changing class name on the parent node.
+ gQueue.push(new changeClass("container2", "link7", "", kShowEvents));
+ gQueue.push(new changeClass("container2", "link7", "displayNone",
+ kHideEvents));
+
+ gQueue.push(new changeClass("container3", "link8", "", kShowEvents));
+ gQueue.push(new changeClass("container3", "link8", "visibilityHidden",
+ kHideEvents));
+
+ gQueue.push(new test1("testContainer"));
+ gQueue.push(new test2("testContainer", "testContainer2"));
+ gQueue.push(new test2("testContainer", "testNestedContainer"));
+ gQueue.push(new test3("testContainer"));
+ gQueue.push(new insertReferredElm("testContainer3"));
+ gQueue.push(new showHiddenParentOfVisibleChild());
+
+ gQueue.push(new hideNDestroyDoc());
+ gQueue.push(new hideHideNDestroyDoc());
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=469985"
+ title=" turn the test from bug 354745 into mochitest">
+ Mozilla Bug 469985</a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=472662"
+ title="no reorder event when html:link display property is changed from 'none' to 'inline'">
+ Mozilla Bug 472662</a>
+ <a target="_blank"
+ title="Rework accessible tree update code"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275">
+ Mozilla Bug 570275</a>
+ <a target="_blank"
+ title="Develop a way to handle visibility style"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=606125">
+ Mozilla Bug 606125</a>
+ <a target="_blank"
+ title="Update accessible tree on content insertion after layout"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=498015">
+ Mozilla Bug 498015</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <div id="eventdump"></div>
+
+ <div id="testContainer">
+ <a id="link1" href="http://www.google.com">Link #1</a>
+ <a id="link2" href="http://www.google.com">Link #2</a>
+ <a id="link3" href="http://www.google.com">Link #3</a>
+ <a id="link4" href="http://www.google.com" style="visibility:collapse">Link #4</a>
+ <a id="link5" href="http://www.google.com">Link #5</a>
+
+ <div id="container" role="list">
+ <span id="child1"></span>
+ <span id="child2" role="listitem"></span>
+ <span id="child3"><span role="listitem"></span></span>
+ <span id="child4"><span id="child4_1" role="listitem"></span><span id="child4_2" role="listitem"></span></span>
+ </div>
+
+ <a id="link6" href="http://www.google.com">Link #6</a>
+
+ <div id="container2" class="displayNone"><a id="link7">Link #7</a></div>
+ <div id="container3" class="visibilityHidden"><a id="link8">Link #8</a></div>
+ <div id="testNestedContainer"></div>
+ </div>
+ <div id="testContainer2"></div>
+ <div id="testContainer3"></div>
+
+ <div id="c4">
+ <div style="visibility:hidden" id="c4_middle">
+ <div style="visibility:visible" id="c4_child"></div>
+ </div>
+
+ <iframe id="c5" src="data:text/html,hey"></iframe>
+ <iframe id="c6" src="data:text/html,<label>l</label><label>l</label>"></iframe>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_mutation.xhtml b/accessible/tests/mochitest/events/test_mutation.xhtml
new file mode 100644
index 000000000..e1aabe612
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_mutation.xhtml
@@ -0,0 +1,97 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+ <title>Accessible mutation events testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <bindings xmlns="http://www.mozilla.org/xbl" >
+ <binding id="button">
+ <content>
+ <button xmlns="http://www.w3.org/1999/xhtml">a button</button>
+ </content>
+ </binding>
+ </bindings>
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ /**
+ * Insert a not accessible bound element containing an accessible element
+ * in anonymous content.
+ */
+ function insertBinding(aContainerID)
+ {
+ this.containerNode = getNode(aContainerID);
+
+ function getButtonFromBinding(aNode)
+ {
+ try { return document.getAnonymousNodes(aNode.firstChild)[0]; }
+ catch (e) { return null; }
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getButtonFromBinding, this.containerNode),
+ new invokerChecker(EVENT_REORDER, this.containerNode)
+ ];
+
+ this.invoke = function insertBinding_invoke()
+ {
+ var span = document.createElement("span");
+ span.setAttribute("style", "-moz-binding:url(#button)");
+ this.containerNode.appendChild(span);
+ }
+
+ this.getID = function insertBinding_getID()
+ {
+ return "insert button binding";
+ }
+ }
+
+ /**
+ * Do tests.
+ */
+ var gQueue = null;
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new insertBinding("testContainer"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=646369"
+ title="UpdateTree should rely on accessible tree walker rather than DOM tree traversal">
+ Mozilla Bug 646369</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <div id="eventdump"></div>
+
+ <div id="testContainer"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_namechange.html b/accessible/tests/mochitest/events/test_namechange.html
new file mode 100644
index 000000000..935d865e9
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_namechange.html
@@ -0,0 +1,123 @@
+<html>
+
+<head>
+ <title>Accessible name change event testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function setAttr(aID, aAttr, aValue, aChecker)
+ {
+ this.eventSeq = [ aChecker ];
+ this.invoke = function setAttr_invoke()
+ {
+ getNode(aID).setAttribute(aAttr, aValue);
+ }
+
+ this.getID = function setAttr_getID()
+ {
+ return "set attr '" + aAttr + "', value '" + aValue + "'";
+ }
+ }
+
+ /**
+ * No name change on an accessible, because the accessible is recreated.
+ */
+ function setAttr_recreate(aID, aAttr, aValue)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getAccessible(aID)),
+ new invokerChecker(EVENT_SHOW, aID)
+ ];
+ this.invoke = function setAttr_recreate_invoke()
+ {
+ todo(false, "No accessible recreation should happen, just name change event");
+ getNode(aID).setAttribute(aAttr, aValue);
+ }
+
+ this.getID = function setAttr_recreate_getID()
+ {
+ return "set attr '" + aAttr + "', value '" + aValue + "'";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ //gA11yEventDumpToConsole = true; // debuggin
+
+ var gQueue = null;
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new setAttr("tst1", "aria-label", "hi",
+ new invokerChecker(EVENT_NAME_CHANGE, "tst1")));
+ gQueue.push(new setAttr("tst1", "aria-labelledby", "display",
+ new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst1")));
+ gQueue.push(new setAttr("tst1", "alt", "alt",
+ new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst1")));
+ gQueue.push(new setAttr("tst1", "title", "title",
+ new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst1")));
+
+ gQueue.push(new setAttr("tst2", "aria-labelledby", "display",
+ new invokerChecker(EVENT_NAME_CHANGE, "tst2")));
+ gQueue.push(new setAttr("tst2", "alt", "alt",
+ new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst2")));
+ gQueue.push(new setAttr("tst2", "title", "title",
+ new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst2")));
+
+ gQueue.push(new setAttr_recreate("tst3", "alt", "alt"));
+ gQueue.push(new setAttr("tst3", "title", "title",
+ new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst3")));
+
+ gQueue.push(new setAttr("tst4", "title", "title",
+ new invokerChecker(EVENT_NAME_CHANGE, "tst4")));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=991969"
+ title="Event not fired when description changes">
+ Bug 991969
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <img id="tst1">
+ <img id="tst2">
+ <img id="tst3">
+ <img id="tst4">
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_namechange.xul b/accessible/tests/mochitest/events/test_namechange.xul
new file mode 100644
index 000000000..9d688585c
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_namechange.xul
@@ -0,0 +1,92 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/chrome-harness.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ /**
+ * Check name changed a11y event.
+ */
+ function nameChangeChecker(aMsg, aID)
+ {
+ this.type = EVENT_NAME_CHANGE;
+
+ function targetGetter()
+ {
+ return getAccessible(aID);
+ }
+ Object.defineProperty(this, "target", { get: targetGetter });
+
+ this.getID = function getID()
+ {
+ return aMsg + " name changed";
+ }
+ }
+
+ function changeRichListItemChild()
+ {
+ this.invoke = function changeRichListItemChild_invoke()
+ {
+ getNode('childcontent').setAttribute('value', 'Changed.');
+ }
+
+ this.eventSeq =
+ [
+ new nameChangeChecker("changeRichListItemChild: ", "listitem")
+ ];
+
+ this.getID = function changeRichListItemChild_getID()
+ {
+ return "changeRichListItemChild";
+ }
+ }
+
+ function doTest()
+ {
+ var queue = new eventQueue();
+ queue.push(new changeRichListItemChild());
+ queue.invoke();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <vbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=986054"
+ title="Propagate name change events">
+ Mozilla Bug 986054
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <richlistbox>
+ <richlistitem id="listitem">
+ <description id="childcontent" value="This will be changed."/>
+ </richlistitem>
+ </richlistbox>
+ </vbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_scroll.xul b/accessible/tests/mochitest/events/test_scroll.xul
new file mode 100644
index 000000000..e33161376
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_scroll.xul
@@ -0,0 +1,131 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/chrome-harness.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../browser.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Tests
+
+ function getAnchorJumpInTabDocument(aTabIdx)
+ {
+ var tabDoc = aTabIdx ? tabDocumentAt(aTabIdx) : currentTabDocument();
+ return tabDoc.querySelector("a[name='link1']");
+ }
+
+ function loadTab(aURL)
+ {
+ this.eventSeq = [
+ new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument),
+ new asyncInvokerChecker(EVENT_SCROLLING_START, getAnchorJumpInTabDocument)
+ ];
+
+ this.invoke = function loadTab_invoke()
+ {
+ tabBrowser().loadURI(aURL);
+ }
+
+ this.getID = function loadTab_getID()
+ {
+ return "load tab: " + aURL;
+ }
+ }
+
+ function loadTabInBackground(aURL)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 1)
+ ];
+
+ this.unexpectedEventSeq = [
+ new invokerChecker(EVENT_SCROLLING_START, getAnchorJumpInTabDocument, 1)
+ ];
+
+ this.invoke = function loadTabInBackground_invoke()
+ {
+ tabBrowser().loadOneTab(aURL, null, "", null, true);
+ }
+
+ this.getID = function loadTabInBackground_getID()
+ {
+ return "load tab in background: " + aURL;
+ }
+ }
+
+ function switchToBackgroundTab()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_SCROLLING_START, getAnchorJumpInTabDocument)
+ ];
+
+ this.invoke = function switchToBackgroundTab_invoke()
+ {
+ tabBrowser().selectTabAtIndex(1);
+ }
+
+ this.getID = function switchToBackgroundTab_getID()
+ {
+ return "switch to background tab";
+ }
+ }
+
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ var url = "http://mochi.test:8888/a11y/accessible/tests/mochitest/events/scroll.html#link1";
+ gQueue.push(new loadTab(url));
+ gQueue.push(new loadTabInBackground(url));
+ gQueue.push(new switchToBackgroundTab());
+ gQueue.onFinish = function() { closeBrowserWindow(); }
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ openBrowserWindow(doTest);
+ ]]>
+ </script>
+
+ <vbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=691734"
+ title="Make sure scrolling start event is fired when document receive focus">
+ Mozilla Bug 691734
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="eventdump"></vbox>
+ </vbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_scroll_caret.xul b/accessible/tests/mochitest/events/test_scroll_caret.xul
new file mode 100644
index 000000000..57e27747f
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_scroll_caret.xul
@@ -0,0 +1,91 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/chrome-harness.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../browser.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Tests
+
+ function getAnchorJumpInTabDocument(aTabIdx)
+ {
+ var tabDoc = aTabIdx ? tabDocumentAt(aTabIdx) : currentTabDocument();
+ return tabDoc.querySelector("h1[id='heading_1']");
+ }
+
+ function loadTab(aURL)
+ {
+ this.eventSeq = [
+ new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument),
+ new asyncCaretMoveChecker(0, getAnchorJumpInTabDocument)
+ ];
+
+ this.invoke = function loadTab_invoke()
+ {
+ tabBrowser().loadURI(aURL);
+ }
+
+ this.getID = function loadTab_getID()
+ {
+ return "load tab: " + aURL;
+ }
+ }
+
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ var url = "http://mochi.test:8888/a11y/accessible/tests/mochitest/events/scroll.html#heading_1";
+ gQueue.push(new loadTab(url));
+ gQueue.onFinish = function() { closeBrowserWindow(); }
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ openBrowserWindow(doTest);
+ ]]>
+ </script>
+
+ <vbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1056459"
+ title="Make sure caret move event is fired when document receive focus">
+ Mozilla Bug 1056459
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="eventdump"></vbox>
+ </vbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_selection.html b/accessible/tests/mochitest/events/test_selection.html
new file mode 100644
index 000000000..de25fedc3
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_selection.html
@@ -0,0 +1,118 @@
+<html>
+
+<head>
+ <title>Accessible selection event testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ //gA11yEventDumpToConsole = true; // debuggin
+
+ var gQueue = null;
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ // open combobox
+ gQueue.push(new synthClick("combobox",
+ new invokerChecker(EVENT_FOCUS, "cb1_item1")));
+ gQueue.push(new synthDownKey("cb1_item1",
+ selChangeSeq("cb1_item1", "cb1_item2")));
+
+ // closed combobox
+ gQueue.push(new synthEscapeKey("combobox",
+ new invokerChecker(EVENT_FOCUS, "combobox")));
+ gQueue.push(new synthDownKey("cb1_item2",
+ selChangeSeq("cb1_item2", "cb1_item3")));
+
+ // listbox
+ gQueue.push(new synthClick("lb1_item1",
+ new invokerChecker(EVENT_SELECTION, "lb1_item1")));
+ gQueue.push(new synthDownKey("lb1_item1",
+ selChangeSeq("lb1_item1", "lb1_item2")));
+
+ // multiselectable listbox
+ gQueue.push(new synthClick("lb2_item1",
+ selChangeSeq(null, "lb2_item1")));
+ gQueue.push(new synthDownKey("lb2_item1",
+ selAddSeq("lb2_item2"),
+ { shiftKey: true }));
+ gQueue.push(new synthUpKey("lb2_item2",
+ selRemoveSeq("lb2_item2"),
+ { shiftKey: true }));
+ gQueue.push(new synthKey("lb2_item1", " ", { ctrlKey: true },
+ selRemoveSeq("lb2_item1")));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=414302"
+ title="Incorrect selection events in HTML, XUL and ARIA">
+ Bug 414302
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=810268"
+ title="There's no way to know unselected item when selection in single selection was changed">
+ Bug 810268
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <select id="combobox">
+ <option id="cb1_item1" value="mushrooms">mushrooms
+ <option id="cb1_item2" value="greenpeppers">green peppers
+ <option id="cb1_item3" value="onions" id="onions">onions
+ <option id="cb1_item4" value="tomatoes">tomatoes
+ <option id="cb1_item5" value="olives">olives
+ </select>
+
+ <select id="listbox" size=5>
+ <option id="lb1_item1" value="mushrooms">mushrooms
+ <option id="lb1_item2" value="greenpeppers">green peppers
+ <option id="lb1_item3" value="onions" id="onions">onions
+ <option id="lb1_item4" value="tomatoes">tomatoes
+ <option id="lb1_item5" value="olives">olives
+ </select>
+
+ <p>Pizza</p>
+ <select id="listbox2" multiple size=5>
+ <option id="lb2_item1" value="mushrooms">mushrooms
+ <option id="lb2_item2" value="greenpeppers">green peppers
+ <option id="lb2_item3" value="onions" id="onions">onions
+ <option id="lb2_item4" value="tomatoes">tomatoes
+ <option id="lb2_item5" value="olives">olives
+ </select>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_selection.xul b/accessible/tests/mochitest/events/test_selection.xul
new file mode 100644
index 000000000..2b0c388ff
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_selection.xul
@@ -0,0 +1,255 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Selection event tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/MochiKit/packed.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ function advanceTab(aTabsID, aDirection, aNextTabID)
+ {
+ var eventSeq1 = [
+ new invokerChecker(EVENT_SELECTION, aNextTabID)
+ ]
+ defineScenario(this, eventSeq1);
+
+ var eventSeq2 = [
+ new invokerChecker(EVENT_HIDE, getAccessible(aNextTabID)),
+ new invokerChecker(EVENT_SHOW, aNextTabID)
+ ];
+ defineScenario(this, eventSeq2);
+
+ this.invoke = function advanceTab_invoke()
+ {
+ todo(false, "No accessible recreation should happen, just selection event");
+ getNode(aTabsID).advanceSelectedTab(aDirection, true);
+ }
+
+ this.getID = function synthFocus_getID()
+ {
+ return "advanceTab on " + prettyName(aTabsID) + " to " + prettyName(aNextTabID);
+ }
+ }
+
+ function select4FirstItems(aID)
+ {
+ this.listboxNode = getNode(aID);
+ this.eventSeq = [
+ new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(0)),
+ new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(1)),
+ new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(2)),
+ new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(3))
+ ];
+
+ this.invoke = function select4FirstItems_invoke()
+ {
+ synthesizeKey("VK_DOWN", { shiftKey: true }); // selects two items
+ synthesizeKey("VK_DOWN", { shiftKey: true });
+ synthesizeKey("VK_DOWN", { shiftKey: true });
+ }
+
+ this.getID = function select4FirstItems_getID()
+ {
+ return "select 4 first items for " + prettyName(aID);
+ }
+ }
+
+ function unselect4FirstItems(aID)
+ {
+ this.listboxNode = getNode(aID);
+ this.eventSeq = [
+ new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(3)),
+ new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(2)),
+ new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(1)),
+ new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(0))
+ ];
+
+ this.invoke = function unselect4FirstItems_invoke()
+ {
+ synthesizeKey("VK_UP", { shiftKey: true });
+ synthesizeKey("VK_UP", { shiftKey: true });
+ synthesizeKey("VK_UP", { shiftKey: true });
+ synthesizeKey(" ", { ctrlKey: true }); // unselect first item
+ }
+
+ this.getID = function unselect4FirstItems_getID()
+ {
+ return "unselect 4 first items for " + prettyName(aID);
+ }
+ }
+
+ function selectAllItems(aID)
+ {
+ this.listboxNode = getNode(aID);
+ this.eventSeq = [
+ new invokerChecker(EVENT_SELECTION_WITHIN, getAccessible(this.listboxNode))
+ ];
+
+ this.invoke = function selectAllItems_invoke()
+ {
+ synthesizeKey("VK_END", { shiftKey: true });
+ }
+
+ this.getID = function selectAllItems_getID()
+ {
+ return "select all items for " + prettyName(aID);
+ }
+ }
+
+ function unselectAllItemsButFirst(aID)
+ {
+ this.listboxNode = getNode(aID);
+ this.eventSeq = [
+ new invokerChecker(EVENT_SELECTION_WITHIN, getAccessible(this.listboxNode))
+ ];
+
+ this.invoke = function unselectAllItemsButFirst_invoke()
+ {
+ synthesizeKey("VK_HOME", { shiftKey: true });
+ }
+
+ this.getID = function unselectAllItemsButFirst_getID()
+ {
+ return "unselect all items for " + prettyName(aID);
+ }
+ }
+
+ function unselectSelectItem(aID)
+ {
+ this.listboxNode = getNode(aID);
+ this.eventSeq = [
+ new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(0)),
+ new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(0))
+ ];
+
+ this.invoke = function unselectSelectItem_invoke()
+ {
+ synthesizeKey(" ", { ctrlKey: true }); // select item
+ synthesizeKey(" ", { ctrlKey: true }); // unselect item
+ }
+
+ this.getID = function unselectSelectItem_getID()
+ {
+ return "unselect and then select first item for " + prettyName(aID);
+ }
+ }
+
+ /**
+ * Do tests.
+ */
+ var gQueue = null;
+
+ //enableLogging("events");
+ //gA11yEventDumpToConsole = true; // debuggin
+
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ //////////////////////////////////////////////////////////////////////////
+ // tabbox
+ gQueue.push(new advanceTab("tabs", 1, "tab3"));
+
+ //////////////////////////////////////////////////////////////////////////
+ // listbox
+ gQueue.push(new synthClick("lb1_item1",
+ new invokerChecker(EVENT_SELECTION, "lb1_item1")));
+ gQueue.push(new synthDownKey("lb1_item1",
+ new invokerChecker(EVENT_SELECTION, "lb1_item2")));
+
+ //////////////////////////////////////////////////////////////////////////
+ // multiselectable listbox
+ gQueue.push(new synthClick("lb2_item1",
+ new invokerChecker(EVENT_SELECTION, "lb2_item1")));
+ gQueue.push(new synthDownKey("lb2_item1",
+ new invokerChecker(EVENT_SELECTION_ADD, "lb2_item2"),
+ { shiftKey: true }));
+ gQueue.push(new synthUpKey("lb2_item2",
+ new invokerChecker(EVENT_SELECTION_REMOVE, "lb2_item2"),
+ { shiftKey: true }));
+ gQueue.push(new synthKey("lb2_item1", " ", { ctrlKey: true },
+ new invokerChecker(EVENT_SELECTION_REMOVE, "lb2_item1")));
+
+ //////////////////////////////////////////////////////////////////////////
+ // selection event coalescence
+
+ // fire 4 selection_add events
+ gQueue.push(new select4FirstItems("listbox2"));
+ // fire 4 selection_remove events
+ gQueue.push(new unselect4FirstItems("listbox2"));
+ // fire selection_within event
+ gQueue.push(new selectAllItems("listbox2"));
+ // fire selection_within event
+ gQueue.push(new unselectAllItemsButFirst("listbox2"));
+ // fire selection_remove/add events
+ gQueue.push(new unselectSelectItem("listbox2"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=414302"
+ title="Incorrect selection events in HTML, XUL and ARIA">
+ Mozilla Bug 414302
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <tabbox id="tabbox" selectedIndex="1">
+ <tabs id="tabs">
+ <tab id="tab1" label="tab1"/>
+ <tab id="tab2" label="tab2"/>
+ <tab id="tab3" label="tab3"/>
+ <tab id="tab4" label="tab4"/>
+ </tabs>
+ <tabpanels>
+ <tabpanel><!-- tabpanel First elements go here --></tabpanel>
+ <tabpanel><button id="b1" label="b1"/></tabpanel>
+ <tabpanel><button id="b2" label="b2"/></tabpanel>
+ <tabpanel></tabpanel>
+ </tabpanels>
+ </tabbox>
+
+ <listbox id="listbox">
+ <listitem id="lb1_item1" label="item1"/>
+ <listitem id="lb1_item2" label="item2"/>
+ </listbox>
+
+ <listbox id="listbox2" seltype="multiple">
+ <listitem id="lb2_item1" label="item1"/>
+ <listitem id="lb2_item2" label="item2"/>
+ <listitem id="lb2_item3" label="item3"/>
+ <listitem id="lb2_item4" label="item4"/>
+ <listitem id="lb2_item5" label="item5"/>
+ <listitem id="lb2_item6" label="item6"/>
+ <listitem id="lb2_item7" label="item7"/>
+ </listbox>
+
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/events/test_selection_aria.html b/accessible/tests/mochitest/events/test_selection_aria.html
new file mode 100644
index 000000000..aabee46fd
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_selection_aria.html
@@ -0,0 +1,127 @@
+<html>
+
+<head>
+ <title>ARIA selection event testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function selectItem(aSelectID, aItemID)
+ {
+ this.selectNode = getNode(aSelectID);
+ this.itemNode = getNode(aItemID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SELECTION, aItemID)
+ ];
+
+ this.invoke = function selectItem_invoke() {
+ var itemNode = this.selectNode.querySelector("*[aria-selected='true']");
+ if (itemNode)
+ itemNode.removeAttribute("aria-selected");
+
+ this.itemNode.setAttribute("aria-selected", "true");
+ }
+
+ this.getID = function selectItem_getID()
+ {
+ return "select item " + prettyName(aItemID);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ var gQueue = null;
+
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new selectItem("tablist", "tab1"));
+ gQueue.push(new selectItem("tablist", "tab2"));
+
+ gQueue.push(new selectItem("tree", "treeitem1"));
+ gQueue.push(new selectItem("tree", "treeitem1a"));
+ gQueue.push(new selectItem("tree", "treeitem1a1"));
+
+ gQueue.push(new selectItem("tree2", "tree2item1"));
+ gQueue.push(new selectItem("tree2", "tree2item1a"));
+ gQueue.push(new selectItem("tree2", "tree2item1a1"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=569653"
+ title="Make selection events async">
+ Mozilla Bug 569653
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=804040"
+ title="Selection event not fired when selection of ARIA tab changes">
+ Mozilla Bug 804040
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div role="tablist" id="tablist">
+ <div role="tab" id="tab1">tab1</div>
+ <div role="tab" id="tab2">tab2</div>
+ </div>
+
+ <div id="tree" role="tree">
+ <div id="treeitem1" role="treeitem">Canada
+ <div id="treeitem1a" role="treeitem">- Ontario
+ <div id="treeitem1a1" role="treeitem">-- Toronto</div>
+ </div>
+ <div id="treeitem1b" role="treeitem">- Manitoba</div>
+ </div>
+ <div id="treeitem2" role="treeitem">Germany</div>
+ <div id="treeitem3" role="treeitem">Russia</div>
+ </div>
+
+ <div id="tree2" role="tree" aria-multiselectable="true">
+ <div id="tree2item1" role="treeitem">Canada
+ <div id="tree2item1a" role="treeitem">- Ontario
+ <div id="tree2item1a1" role="treeitem">-- Toronto</div>
+ </div>
+ <div id="tree2item1b" role="treeitem">- Manitoba</div>
+ </div>
+ <div id="tree2item2" role="treeitem">Germany</div>
+ <div id="tree2item3" role="treeitem">Russia</div>
+ </div>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_statechange.html b/accessible/tests/mochitest/events/test_statechange.html
new file mode 100644
index 000000000..f4557376b
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_statechange.html
@@ -0,0 +1,287 @@
+<html>
+
+<head>
+ <title>Accessible state change event testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function makeEditableDoc(aDocNode, aIsEnabled)
+ {
+ this.DOMNode = aDocNode;
+
+ this.invoke = function editabledoc_invoke() {
+ // Note: this should fire an EVENT_STATE_CHANGE
+ this.DOMNode.designMode = 'on';
+ };
+
+ this.check = function editabledoc_check(aEvent) {
+
+ testStates(aDocNode, 0, EXT_STATE_EDITABLE);
+
+ var event = null;
+ try {
+ var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
+ } catch (e) {
+ ok(false, "State change event was expected");
+ }
+
+ if (!event) { return; }
+
+ ok(event.isExtraState, "Extra state change was expected");
+ is(event.state, EXT_STATE_EDITABLE, "Wrong state of statechange event");
+ ok(event.isEnabled, "Expected editable state to be enabled");
+ }
+
+ this.getID = function editabledoc_getID() {
+ return prettyName(aDocNode) + " editable state changed";
+ };
+ }
+
+ function invalidInput(aNodeOrID)
+ {
+ this.DOMNode = getNode(aNodeOrID);
+
+ this.invoke = function invalidInput_invoke() {
+ // Note: this should fire an EVENT_STATE_CHANGE
+ this.DOMNode.value = "I am not an email";
+ };
+
+ this.check = function invalidInput_check() {
+ testStates(aNodeOrID, STATE_INVALID);
+ };
+
+ this.getID = function invalidInput_getID() {
+ return prettyName(aNodeOrID) + " became invalid";
+ };
+ }
+
+ function changeCheckInput(aID, aIsChecked)
+ {
+ this.DOMNode = getNode(aID);
+
+ this.eventSeq = [
+ new stateChangeChecker(STATE_CHECKED, false, aIsChecked, this.DOMNode)
+ ];
+
+ this.invoke = function changeCheckInput_invoke()
+ {
+ this.DOMNode.checked = aIsChecked;
+ }
+
+ this.getID = function changeCheckInput_getID()
+ {
+ return "change checked state to '" + aIsChecked + "' for " +
+ prettyName(aID);
+ }
+ }
+
+ function stateChangeOnFileInput(aID, aAttr, aValue,
+ aState, aIsExtraState, aIsEnabled)
+ {
+ this.fileControlNode = getNode(aID);
+ this.fileControl = getAccessible(this.fileControlNode);
+ this.browseButton = this.fileControl.firstChild;
+ // No state change events on the label.
+
+ this.invoke = function stateChangeOnFileInput_invoke()
+ {
+ this.fileControlNode.setAttribute(aAttr, aValue);
+ }
+
+ this.eventSeq = [
+ new stateChangeChecker(aState, aIsExtraState, aIsEnabled, this.fileControl),
+ new stateChangeChecker(aState, aIsExtraState, aIsEnabled, this.browseButton)
+ ];
+
+ this.getID = function stateChangeOnFileInput_getID()
+ {
+ return "inherited state change on file input on attribute '" + aAttr + "' change";
+ }
+ }
+
+ function dupeStateChange(aID, aAttr, aValue,
+ aState, aIsExtraState, aIsEnabled)
+ {
+ this.eventSeq = [
+ new stateChangeChecker(aState, aIsExtraState, aIsEnabled, getNode(aID))
+ ];
+
+ this.invoke = function dupeStateChange_invoke()
+ {
+ getNode(aID).setAttribute(aAttr, aValue);
+ getNode(aID).setAttribute(aAttr, aValue);
+ }
+
+ this.getID = function dupeStateChange_getID()
+ {
+ return "duped state change events";
+ }
+ }
+
+ function oppositeStateChange(aID, aAttr, aState, aIsExtraState)
+ {
+ this.eventSeq = [ ];
+ this.unexpectedEventSeq = [
+ new stateChangeChecker(aState, aIsExtraState, false, getNode(aID)),
+ new stateChangeChecker(aState, aIsExtraState, true, getNode(aID))
+ ];
+
+ this.invoke = function oppositeStateChange_invoke()
+ {
+ getNode(aID).setAttribute(aAttr, "false");
+ getNode(aID).setAttribute(aAttr, "true");
+ }
+
+ this.getID = function oppositeStateChange_getID()
+ {
+ return "opposite state change events";
+ }
+ }
+
+ /**
+ * Change concomitant ARIA and native attribute at once.
+ */
+ function echoingStateChange(aID, aARIAAttr, aAttr, aValue,
+ aState, aIsExtraState, aIsEnabled)
+ {
+ this.eventSeq = [
+ new stateChangeChecker(aState, aIsExtraState, aIsEnabled, getNode(aID))
+ ];
+
+ this.invoke = function echoingStateChange_invoke()
+ {
+ if (aValue == null) {
+ getNode(aID).removeAttribute(aARIAAttr);
+ getNode(aID).removeAttribute(aAttr);
+
+ } else {
+ getNode(aID).setAttribute(aARIAAttr, aValue);
+ getNode(aID).setAttribute(aAttr, aValue);
+ }
+ }
+
+ this.getID = function echoingStateChange_getID()
+ {
+ return "enchoing ARIA and native attributes change";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ var gQueue = null;
+
+ // var gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ function doTests()
+ {
+ gQueue = new eventQueue(nsIAccessibleEvent.EVENT_STATE_CHANGE);
+
+ // Test delayed editable state change
+ var doc = document.getElementById("iframe").contentDocument;
+ gQueue.push(new makeEditableDoc(doc));
+
+ // invalid state change
+ gQueue.push(new invalidInput("email"));
+
+ // checked state change
+ gQueue.push(new changeCheckInput("checkbox", true));
+ gQueue.push(new changeCheckInput("checkbox", false));
+ gQueue.push(new changeCheckInput("radio", true));
+ gQueue.push(new changeCheckInput("radio", false));
+
+ // file input inherited state changes
+ gQueue.push(new stateChangeOnFileInput("file", "aria-busy", "true",
+ STATE_BUSY, false, true));
+ gQueue.push(new stateChangeOnFileInput("file", "aria-required", "true",
+ STATE_REQUIRED, false, true));
+ gQueue.push(new stateChangeOnFileInput("file", "aria-invalid", "true",
+ STATE_INVALID, false, true));
+
+ gQueue.push(new dupeStateChange("div", "aria-busy", "true",
+ STATE_BUSY, false, true));
+ gQueue.push(new oppositeStateChange("div", "aria-busy",
+ STATE_BUSY, false));
+
+ gQueue.push(new echoingStateChange("text1", "aria-disabled", "disabled", "true",
+ EXT_STATE_ENABLED, true, false));
+ gQueue.push(new echoingStateChange("text1", "aria-disabled", "disabled", null,
+ EXT_STATE_ENABLED, true, true));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=564471"
+ title="Make state change events async">
+ Bug 564471
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=555728"
+ title="Fire a11y event based on HTML5 constraint validation">
+ Bug 555728
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=699017"
+ title="File input control should be propogate states to descendants">
+ Bug 699017
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=788389"
+ title="Fire statechange event whenever checked state is changed not depending on focused state">
+ Bug 788389
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=926812"
+ title="State change event not fired when both disabled and aria-disabled are toggled">
+ Bug 926812
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="testContainer">
+ <iframe id="iframe"></iframe>
+ </div>
+
+ <input id="email" type='email'>
+
+ <input id="checkbox" type="checkbox">
+ <input id="radio" type="radio">
+
+ <input id="file" type="file">
+
+ <div id="div"></div>
+
+ <input id="text1">
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_text.html b/accessible/tests/mochitest/events/test_text.html
new file mode 100644
index 000000000..d992d6c64
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_text.html
@@ -0,0 +1,339 @@
+<html>
+
+<head>
+ <title>Accessible mutation events testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ /**
+ * Base text remove invoker and checker.
+ */
+ function textRemoveInvoker(aID, aStart, aEnd, aText)
+ {
+ this.DOMNode = getNode(aID);
+
+ this.eventSeq = [
+ new textChangeChecker(aID, aStart, aEnd, aText, false)
+ ];
+ }
+
+ function textInsertInvoker(aID, aStart, aEnd, aText)
+ {
+ this.DOMNode = getNode(aID);
+
+ this.eventSeq = [
+ new textChangeChecker(aID, aStart, aEnd, aText, true)
+ ];
+ }
+
+ /**
+ * Remove inaccessible child node containing accessibles.
+ */
+ function removeChildSpan(aID)
+ {
+ this.__proto__ = new textRemoveInvoker(aID, 0, 5, "33322");
+
+ this.invoke = function removeChildSpan_invoke()
+ {
+ // remove HTML span, a first child of the node
+ this.DOMNode.removeChild(this.DOMNode.firstChild);
+ }
+
+ this.getID = function removeChildSpan_getID()
+ {
+ return "Remove inaccessible span containing accessible nodes" + prettyName(aID);
+ }
+ }
+
+ /**
+ * Insert inaccessible child node containing accessibles.
+ */
+ function insertChildSpan(aID, aInsertAllTogether)
+ {
+ this.__proto__ = new textInsertInvoker(aID, 0, 5, "33322");
+
+ this.invoke = function insertChildSpan_invoke()
+ {
+ // <span><span>333</span><span>22</span></span>
+ if (aInsertAllTogether) {
+ var topSpan = document.createElement("span");
+ var fSpan = document.createElement("span");
+ fSpan.textContent = "333";
+ topSpan.appendChild(fSpan);
+ var sSpan = document.createElement("span");
+ sSpan.textContent = "22";
+ topSpan.appendChild(sSpan);
+
+ this.DOMNode.insertBefore(topSpan, this.DOMNode.childNodes[0]);
+
+ } else {
+ var topSpan = document.createElement("span");
+ this.DOMNode.insertBefore(topSpan, this.DOMNode.childNodes[0]);
+
+ var fSpan = document.createElement("span");
+ fSpan.textContent = "333";
+ topSpan.appendChild(fSpan);
+
+ var sSpan = document.createElement("span");
+ sSpan.textContent = "22";
+ topSpan.appendChild(sSpan);
+ }
+ }
+
+ this.getID = function insertChildSpan_getID()
+ {
+ return "Insert inaccessible span containing accessibles" +
+ prettyName(aID);
+ }
+ }
+
+ /**
+ * Remove child embedded accessible.
+ */
+ function removeChildDiv(aID)
+ {
+ this.__proto__ = new textRemoveInvoker(aID, 5, 6, kEmbedChar);
+
+ this.invoke = function removeChildDiv_invoke()
+ {
+ var childDiv = this.DOMNode.childNodes[1];
+
+ // Ensure accessible is created to get text remove event when it's
+ // removed.
+ getAccessible(childDiv);
+
+ this.DOMNode.removeChild(childDiv);
+ }
+
+ this.getID = function removeChildDiv_getID()
+ {
+ return "Remove accessible div from the middle of text accessible " +
+ prettyName(aID);
+ }
+ }
+
+ /**
+ * Insert child embedded accessible.
+ */
+ function insertChildDiv(aID)
+ {
+ this.__proto__ = new textInsertInvoker(aID, 5, 6, kEmbedChar);
+
+ this.invoke = function insertChildDiv_invoke()
+ {
+ var childDiv = document.createElement("div");
+ this.DOMNode.insertBefore(childDiv, this.DOMNode.childNodes[1]);
+ }
+
+ this.getID = function insertChildDiv_getID()
+ {
+ return "Insert accessible div into the middle of text accessible " +
+ prettyName(aID);
+ }
+ }
+
+ /**
+ * Remove children from text container from first to last child or vice
+ * versa.
+ */
+ function removeChildren(aID, aLastToFirst, aStart, aEnd, aText)
+ {
+ this.__proto__ = new textRemoveInvoker(aID, aStart, aEnd, aText);
+
+ this.invoke = function removeChildren_invoke()
+ {
+ if (aLastToFirst) {
+ while (this.DOMNode.firstChild)
+ this.DOMNode.removeChild(this.DOMNode.lastChild);
+ } else {
+ while (this.DOMNode.firstChild)
+ this.DOMNode.removeChild(this.DOMNode.firstChild);
+ }
+ }
+
+ this.getID = function removeChildren_getID()
+ {
+ return "remove children of " + prettyName(aID) +
+ (aLastToFirst ? " from last to first" : " from first to last");
+ }
+ }
+
+ /**
+ * Remove text from HTML input.
+ */
+ function removeTextFromInput(aID, aStart, aEnd, aText)
+ {
+ this.__proto__ = new textRemoveInvoker(aID, aStart, aEnd, aText);
+
+ this.eventSeq.push(new invokerChecker(EVENT_TEXT_VALUE_CHANGE,
+ this.DOMNode));
+
+ this.invoke = function removeTextFromInput_invoke()
+ {
+ const nsIDOMNSEditableElement =
+ Components.interfaces.nsIDOMNSEditableElement;
+
+ this.DOMNode.focus();
+ this.DOMNode.setSelectionRange(aStart, aEnd);
+
+ synthesizeKey("VK_DELETE", {});
+ }
+
+ this.getID = function removeTextFromInput_getID()
+ {
+ return "Remove text from " + aStart + " to " + aEnd + " for " +
+ prettyName(aID);
+ }
+ }
+
+ /**
+ * Add text into HTML input.
+ */
+ function insertTextIntoInput(aID, aStart, aEnd, aText)
+ {
+ this.__proto__ = new textInsertInvoker(aID, aStart, aEnd, aText);
+
+ this.eventSeq.push(new invokerChecker(EVENT_TEXT_VALUE_CHANGE,
+ this.DOMNode));
+
+ this.invoke = function insertTextIntoInput_invoke()
+ {
+ this.DOMNode.focus();
+ synthesizeKey("a", {});
+ }
+
+ this.getID = function insertTextIntoInput_getID()
+ {
+ return "Insert text to " + aStart + " for " + prettyName(aID);
+ }
+ }
+
+ /**
+ * Remove text data from text node of editable area.
+ */
+ function removeTextFromEditable(aID, aStart, aEnd, aText, aTextNode)
+ {
+ this.__proto__ = new textRemoveInvoker(aID, aStart, aEnd, aText);
+
+ this.invoke = function removeTextFromEditable_invoke()
+ {
+ this.DOMNode.focus();
+
+ var selection = window.getSelection();
+ var range = document.createRange();
+ range.setStart(this.textNode, aStart);
+ range.setEnd(this.textNode, aEnd);
+ selection.addRange(range);
+
+ synthesizeKey("VK_DELETE", {});
+ }
+
+ this.getID = function removeTextFromEditable_getID()
+ {
+ return "Remove text from " + aStart + " to " + aEnd + " for " +
+ prettyName(aID);
+ }
+
+ this.textNode = getNode(aTextNode);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Do tests
+ gA11yEventDumpToConsole = true; // debugging
+
+ var gQueue = null;
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ // Text remove event on inaccessible child HTML span removal containing
+ // accessible text nodes.
+ gQueue.push(new removeChildSpan("p"));
+ gQueue.push(new insertChildSpan("p"), true);
+ gQueue.push(new insertChildSpan("p"), false);
+
+ // Remove embedded character.
+ gQueue.push(new removeChildDiv("div"));
+ gQueue.push(new insertChildDiv("div"));
+
+ // Remove all children.
+ var text = kEmbedChar + "txt" + kEmbedChar;
+ gQueue.push(new removeChildren("div2", true, 0, 5, text));
+ gQueue.push(new removeChildren("div3", false, 0, 5, text));
+
+ // Text remove from text node within hypertext accessible.
+ gQueue.push(new removeTextFromInput("input", 1, 3, "al"));
+ gQueue.push(new insertTextIntoInput("input", 1, 2, "a"));
+
+ // bug 570691
+ todo(false, "Fix text change events from editable area, see bug 570691");
+ //var textNode = getNode("editable").firstChild;
+ //gQueue.push(new removeTextFromEditable("editable", 1, 3, "al", textNode));
+ //textNode = getNode("editable2").firstChild.firstChild;
+ //gQueue.push(new removeTextFromEditable("editable2", 1, 3, "al", textNode));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=566293"
+ title=" wrong length of text remove event when inaccessible node containing accessible nodes is removed">
+ Mozilla Bug 566293
+ </a><br>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=570710"
+ title="Avoid extra array traversal during text event creation">
+ Mozilla Bug 570710
+ </a><br>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=574003"
+ title="Coalesce text events on nodes removal">
+ Mozilla Bug 574003
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=575052"
+ title="Cache text offsets within hypertext accessible">
+ Mozilla Bug 575052
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275"
+ title="Rework accessible tree update code">
+ Mozilla Bug 570275
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <p id="p"><span><span>333</span><span>22</span></span>1111</p>
+ <div id="div">hello<div>hello</div>hello</div>
+ <div id="div2"><div>txt</div>txt<div>txt</div></div>
+ <div id="div3"><div>txt</div>txt<div>txt</div></div>
+ <input id="input" value="value">
+ <div contentEditable="true" id="editable">value</div>
+ <div contentEditable="true" id="editable2"><span>value</span></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_text_alg.html b/accessible/tests/mochitest/events/test_text_alg.html
new file mode 100644
index 000000000..97428fb3b
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_text_alg.html
@@ -0,0 +1,249 @@
+<html>
+
+<head>
+ <title>Accessible text update algorithm testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ const kRemoval = false;
+ const kInsertion = true;
+ const kUnexpected = true;
+
+ function changeText(aContainerID, aValue, aEventList)
+ {
+ this.containerNode = getNode(aContainerID);
+ this.textNode = this.containerNode.firstChild;
+ this.textData = this.textNode.data;
+
+ this.eventSeq = [ ];
+ this.unexpectedEventSeq = [ ];
+
+ for (var i = 0; i < aEventList.length; i++) {
+ var event = aEventList[i];
+
+ var isInserted = event[0];
+ var str = event[1];
+ var offset = event[2];
+ var checker = new textChangeChecker(this.containerNode, offset,
+ offset + str.length, str,
+ isInserted);
+
+ if (event[3] == kUnexpected)
+ this.unexpectedEventSeq.push(checker);
+ else
+ this.eventSeq.push(checker);
+ }
+
+ this.invoke = function changeText_invoke()
+ {
+ this.textNode.data = aValue;
+ }
+
+ this.getID = function changeText_getID()
+ {
+ return "change text '" + shortenString(this.textData) + "' -> '" +
+ shortenString(this.textNode.data) + "' for " +
+ prettyName(this.containerNode);
+ }
+ }
+
+ function expStr(x, doublings)
+ {
+ for (var i = 0; i < doublings; ++i)
+ x = x + x;
+ return x;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ //////////////////////////////////////////////////////////////////////////
+ // wqrema -> tqb: substitution coalesced with removal
+
+ var events = [
+ [ kRemoval, "w", 0 ], // wqrema -> qrema
+ [ kInsertion, "t", 0], // qrema -> tqrema
+ [ kRemoval, "rema", 2 ], // tqrema -> tq
+ [ kInsertion, "b", 2] // tq -> tqb
+ ];
+ gQueue.push(new changeText("p1", "tqb", events));
+
+ //////////////////////////////////////////////////////////////////////////
+ // b -> insa: substitution coalesced with insertion (complex substitution)
+
+ events = [
+ [ kRemoval, "b", 0 ], // b ->
+ [ kInsertion, "insa", 0] // -> insa
+ ];
+ gQueue.push(new changeText("p2", "insa", events));
+
+ //////////////////////////////////////////////////////////////////////////
+ // abc -> def: coalesced substitutions
+
+ events = [
+ [ kRemoval, "abc", 0 ], // abc ->
+ [ kInsertion, "def", 0] // -> def
+ ];
+ gQueue.push(new changeText("p3", "def", events));
+
+ //////////////////////////////////////////////////////////////////////////
+ // abcabc -> abcDEFabc: coalesced insertions
+
+ events = [
+ [ kInsertion, "DEF", 3] // abcabc -> abcDEFabc
+ ];
+ gQueue.push(new changeText("p4", "abcDEFabc", events));
+
+ //////////////////////////////////////////////////////////////////////////
+ // abc -> defabc: insertion into begin
+
+ events = [
+ [ kInsertion, "def", 0] // abc -> defabc
+ ];
+ gQueue.push(new changeText("p5", "defabc", events));
+
+ //////////////////////////////////////////////////////////////////////////
+ // abc -> abcdef: insertion into end
+
+ events = [
+ [ kInsertion, "def", 3] // abc -> abcdef
+ ];
+ gQueue.push(new changeText("p6", "abcdef", events));
+
+ //////////////////////////////////////////////////////////////////////////
+ // defabc -> abc: removal from begin
+
+ events = [
+ [ kRemoval, "def", 0] // defabc -> abc
+ ];
+ gQueue.push(new changeText("p7", "abc", events));
+
+ //////////////////////////////////////////////////////////////////////////
+ // abcdef -> abc: removal from the end
+
+ events = [
+ [ kRemoval, "def", 3] // abcdef -> abc
+ ];
+ gQueue.push(new changeText("p8", "abc", events));
+
+ //////////////////////////////////////////////////////////////////////////
+ // abcDEFabc -> abcabc: coalesced removals
+
+ events = [
+ [ kRemoval, "DEF", 3] // abcDEFabc -> abcabc
+ ];
+ gQueue.push(new changeText("p9", "abcabc", events));
+
+ //////////////////////////////////////////////////////////////////////////
+ // !abcdef@ -> @axbcef!: insertion, deletion and substitutions
+
+ events = [
+ [ kRemoval, "!", 0 ], // !abcdef@ -> abcdef@
+ [ kInsertion, "@", 0], // abcdef@ -> @abcdef@
+ [ kInsertion, "x", 2 ], // @abcdef@ -> @axbcdef@
+ [ kRemoval, "d", 5], // @axbcdef@ -> @axbcef@
+ [ kRemoval, "@", 7 ], // @axbcef@ -> @axbcef
+ [ kInsertion, "!", 7 ], // @axbcef -> @axbcef!
+ ];
+ gQueue.push(new changeText("p10", "@axbcef!", events));
+
+ //////////////////////////////////////////////////////////////////////////
+ // meilenstein -> levenshtein: insertion, complex and simple substitutions
+
+ events = [
+ [ kRemoval, "m", 0 ], // meilenstein -> eilenstein
+ [ kInsertion, "l", 0], // eilenstein -> leilenstein
+ [ kRemoval, "il", 2 ], // leilenstein -> leenstein
+ [ kInsertion, "v", 2], // leenstein -> levenstein
+ [ kInsertion, "h", 6 ], // levenstein -> levenshtein
+ ];
+ gQueue.push(new changeText("p11", "levenshtein", events));
+
+ //////////////////////////////////////////////////////////////////////////
+ // long strings, remove/insert pair as the old string was replaced on
+ // new one
+
+ var longStr1 = expStr("x", 16);
+ var longStr2 = expStr("X", 16);
+
+ var newStr = "a" + longStr1 + "b", insStr = longStr1, rmStr = "";
+ events = [
+ [ kRemoval, rmStr, 1, kUnexpected ],
+ [ kInsertion, insStr, 1 ]
+ ];
+ gQueue.push(new changeText("p12", newStr, events));
+
+ newStr = "a" + longStr2 + "b", insStr = longStr2, rmStr = longStr1;
+ events = [
+ [ kRemoval, rmStr, 1 ],
+ [ kInsertion, insStr, 1]
+ ];
+ gQueue.push(new changeText("p12", newStr, events));
+
+ newStr = "ab", insStr = "", rmStr = longStr2;
+ events = [
+ [ kRemoval, rmStr, 1 ],
+ [ kInsertion, insStr, 1, kUnexpected ]
+ ];
+ gQueue.push(new changeText("p12", newStr, events));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=626660"
+ title="Cache rendered text on a11y side">
+ Mozilla Bug 626660
+ </a>
+ <br>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <div id="eventdump"></div>
+
+ <p id="p1">wqrema</p>
+ <p id="p2">b</p>
+ <p id="p3">abc</p>
+ <p id="p4">abcabc</p>
+ <p id="p5">abc</p>
+ <p id="p6">abc</p>
+ <p id="p7">defabc</p>
+ <p id="p8">abcdef</p>
+ <p id="p9">abcDEFabc</p>
+ <p id="p10">!abcdef@</p>
+ <p id="p11">meilenstein</p>
+ <p id="p12">ab</p>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_textattrchange.html b/accessible/tests/mochitest/events/test_textattrchange.html
new file mode 100644
index 000000000..a69a8df15
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_textattrchange.html
@@ -0,0 +1,115 @@
+<html>
+
+<head>
+ <title>Text attribute changed event for misspelled text</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript">
+
+ const nsIDOMNSEditableElement =
+ Components.interfaces.nsIDOMNSEditableElement;
+
+ Components.utils.import("resource://gre/modules/InlineSpellChecker.jsm");
+
+ function spelledTextInvoker(aID)
+ {
+ this.DOMNode = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_TEXT_ATTRIBUTE_CHANGED, this.DOMNode)
+ ];
+
+ this.invoke = function spelledTextInvoker_invoke()
+ {
+ var editor = this.DOMNode.QueryInterface(nsIDOMNSEditableElement).editor;
+ var spellChecker = new InlineSpellChecker(editor);
+ spellChecker.enabled = true;
+
+ //var spellchecker = editor.getInlineSpellChecker(true);
+ //spellchecker.enableRealTimeSpell = true;
+
+ this.DOMNode.value = "valid text inalid tixt";
+ }
+
+ this.finalCheck = function spelledTextInvoker_finalCheck()
+ {
+ var defAttrs = buildDefaultTextAttrs(this.DOMNode, kInputFontSize,
+ kNormalFontWeight,
+ kInputFontFamily);
+ testDefaultTextAttrs(aID, defAttrs);
+
+ var attrs = { };
+ var misspelledAttrs = {
+ "invalid": "spelling"
+ };
+
+ testTextAttrs(aID, 0, attrs, defAttrs, 0, 11);
+ testTextAttrs(aID, 11, misspelledAttrs, defAttrs, 11, 17);
+ testTextAttrs(aID, 17, attrs, defAttrs, 17, 18);
+ testTextAttrs(aID, 18, misspelledAttrs, defAttrs, 18, 22);
+ }
+
+ this.getID = function spelledTextInvoker_getID()
+ {
+ return "text attribute change for misspelled text";
+ }
+ }
+
+ /**
+ * Do tests.
+ */
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+ function doTests()
+ {
+ // Synth focus before spellchecking turning on to make sure editor
+ // gets a time for initialization.
+
+ gQueue = new eventQueue();
+ gQueue.push(new synthFocus("input"));
+ gQueue.push(new spelledTextInvoker("input"));
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=345759"
+ title="Implement text attributes">
+ Mozilla Bug 345759
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="input"/>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_textselchange.html b/accessible/tests/mochitest/events/test_textselchange.html
new file mode 100644
index 000000000..92ac30423
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_textselchange.html
@@ -0,0 +1,86 @@
+<html>
+
+<head>
+ <title>Accessible text selection change events testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ var gQueue = null;
+
+ // gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ function getOnclickSeq(aID)
+ {
+ return [
+ new caretMoveChecker(0, aID),
+ new unexpectedInvokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID)
+ ];
+ }
+
+ function doTests()
+ {
+ // test caret move events and caret offsets
+ gQueue = new eventQueue();
+
+ gQueue.push(new synthClick("c1_p1", getOnclickSeq("c1_p1")));
+ gQueue.push(new synthDownKey("c1", new textSelectionChecker("c1", 0, 1), { shiftKey: true }));
+ gQueue.push(new synthDownKey("c1", new textSelectionChecker("c1", 0, 2), { shiftKey: true }));
+
+ gQueue.push(new synthClick("ta1", getOnclickSeq("ta1")));
+ gQueue.push(new synthRightKey("ta1",
+ new textSelectionChecker("ta1", 0, 1),
+ { shiftKey: true }));
+ gQueue.push(new synthLeftKey("ta1",
+ [new textSelectionChecker("ta1", 0, 0),
+ new caretMoveChecker(0, "ta1")]));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=762934"
+ title="Text selection change event has a wrong target when selection is spanned through several objects">
+ Bug 762934
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=956032"
+ title="Text selection change event missed when selected text becomes unselected">
+ Bug 956032
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="c1" contentEditable="true">
+ <p id="c1_p1">paragraph</p>
+ <p id="c1_p2">paragraph</p>
+ </div>
+
+ <textarea id="ta1">Hello world</textarea>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/events/test_tree.xul b/accessible/tests/mochitest/events/test_tree.xul
new file mode 100644
index 000000000..797e7bf97
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_tree.xul
@@ -0,0 +1,348 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="DOM TreeRowCountChanged and a11y name change events.">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invoker's checkers
+
+ /**
+ * Check TreeRowCountChanged event.
+ */
+ function rowCountChangedChecker(aMsg, aIdx, aCount)
+ {
+ this.type = "TreeRowCountChanged";
+ this.target = gTree;
+ this.check = function check(aEvent)
+ {
+ var propBag = aEvent.detail.QueryInterface(Components.interfaces.nsIPropertyBag2);
+ var index = propBag.getPropertyAsInt32("index");
+ is(index, aIdx, "Wrong 'index' data of 'treeRowCountChanged' event.");
+
+ var count = propBag.getPropertyAsInt32("count");
+ is(count, aCount, "Wrong 'count' data of 'treeRowCountChanged' event.");
+ }
+ this.getID = function getID()
+ {
+ return aMsg + "TreeRowCountChanged";
+ }
+ }
+
+ /**
+ * Check TreeInvalidated event.
+ */
+ function treeInvalidatedChecker(aMsg, aStartRow, aEndRow, aStartCol, aEndCol)
+ {
+ this.type = "TreeInvalidated";
+ this.target = gTree;
+ this.check = function check(aEvent)
+ {
+ var propBag = aEvent.detail.QueryInterface(Components.interfaces.nsIPropertyBag2);
+ try {
+ var startRow = propBag.getPropertyAsInt32("startrow");
+ } catch (e if e.name == 'NS_ERROR_NOT_AVAILABLE') {
+ startRow = null;
+ }
+ is(startRow, aStartRow,
+ "Wrong 'startrow' of 'treeInvalidated' event on " + aMsg);
+
+ try {
+ var endRow = propBag.getPropertyAsInt32("endrow");
+ } catch (e if e.name == 'NS_ERROR_NOT_AVAILABLE') {
+ endRow = null;
+ }
+ is(endRow, aEndRow,
+ "Wrong 'endrow' of 'treeInvalidated' event on " + aMsg);
+
+ try {
+ var startCol = propBag.getPropertyAsInt32("startcolumn");
+ } catch (e if e.name == 'NS_ERROR_NOT_AVAILABLE') {
+ startCol = null;
+ }
+ is(startCol, aStartCol,
+ "Wrong 'startcolumn' of 'treeInvalidated' event on " + aMsg);
+
+ try {
+ var endCol = propBag.getPropertyAsInt32("endcolumn");
+ } catch (e if e.name == 'NS_ERROR_NOT_AVAILABLE') {
+ endCol = null;
+ }
+ is(endCol, aEndCol,
+ "Wrong 'endcolumn' of 'treeInvalidated' event on " + aMsg);
+ }
+ this.getID = function getID()
+ {
+ return "TreeInvalidated on " + aMsg;
+ }
+ }
+
+ /**
+ * Check name changed a11y event.
+ */
+ function nameChangeChecker(aMsg, aRow, aCol)
+ {
+ this.type = EVENT_NAME_CHANGE;
+
+ function targetGetter()
+ {
+ var acc = getAccessible(gTree);
+
+ var tableAcc = getAccessible(acc, [nsIAccessibleTable]);
+ return tableAcc.getCellAt(aRow, aCol);
+ }
+ Object.defineProperty(this, "target", { get: targetGetter });
+
+ this.getID = function getID()
+ {
+ return aMsg + "name changed";
+ }
+ }
+
+ /**
+ * Check name changed a11y event for a row.
+ */
+ function rowNameChangeChecker(aMsg, aRow)
+ {
+ this.type = EVENT_NAME_CHANGE;
+
+ function targetGetter()
+ {
+ var acc = getAccessible(gTree);
+ return acc.getChildAt(aRow + 1);
+ }
+ Object.defineProperty(this, "target", { get: targetGetter });
+
+ this.getID = function getID()
+ {
+ return aMsg + "name changed";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ /**
+ * Set tree view.
+ */
+ function setTreeView()
+ {
+ this.invoke = function setTreeView_invoke()
+ {
+ gTreeBox.view = gView;
+ }
+
+ this.getID = function setTreeView_getID() { return "set tree view"; }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, gTree)
+ ];
+ };
+
+ /**
+ * Insert row at 0 index and checks TreeRowCountChanged and TreeInvalidated
+ * event.
+ */
+ function insertRow()
+ {
+ this.invoke = function insertRow_invoke()
+ {
+ gView.appendItem("last");
+ gTreeBox.rowCountChanged(0, 1);
+ }
+
+ this.eventSeq =
+ [
+ new rowCountChangedChecker("insertRow: ", 0, 1),
+ new treeInvalidatedChecker("insertRow", 0, 5, null, null)
+ ];
+
+ this.getID = function insertRow_getID()
+ {
+ return "insert row";
+ }
+ }
+
+ /**
+ * Invalidates first column and checks six name changed events for each
+ * treeitem plus TreeInvalidated event.
+ */
+ function invalidateColumn()
+ {
+ this.invoke = function invalidateColumn_invoke()
+ {
+ // Make sure accessible subtree of XUL tree is created otherwise no
+ // name change events for cell accessibles are emitted.
+ var tree = getAccessible(gTree);
+ var child = tree.firstChild;
+ var walkDown = true;
+ while (child != tree) {
+ if (walkDown) {
+ var grandChild = child.firstChild;
+ if (grandChild) {
+ child = grandChild;
+ continue;
+ }
+ }
+
+ var sibling = child.nextSibling;
+ if (sibling) {
+ child = sibling;
+ walkDown = true;
+ continue;
+ }
+
+ child = child.parent;
+ walkDown = false;
+ }
+
+ // Fire 'TreeInvalidated' event by InvalidateColumn()
+ var firstCol = gTree.columns.getFirstColumn();
+ for (var i = 0; i < gView.rowCount; i++)
+ gView.setCellText(i, firstCol, "hey " + String(i) + "x0");
+
+ gTreeBox.invalidateColumn(firstCol);
+ }
+
+ this.eventSeq =
+ [
+ new nameChangeChecker("invalidateColumn: ", 0, 0),
+ new nameChangeChecker("invalidateColumn: ", 1, 0),
+ new nameChangeChecker("invalidateColumn: ", 2, 0),
+ new nameChangeChecker("invalidateColumn: ", 3, 0),
+ new nameChangeChecker("invalidateColumn: ", 4, 0),
+ new nameChangeChecker("invalidateColumn: ", 5, 0),
+ new treeInvalidatedChecker("invalidateColumn", null, null, 0, 0)
+ ];
+
+ this.getID = function invalidateColumn_getID()
+ {
+ return "invalidate column";
+ }
+ }
+
+ /**
+ * Invalidates second row and checks name changed event for first treeitem
+ * (note, there are two name changed events on linux due to different
+ * accessible tree for xul:tree element) plus TreeInvalidated event.
+ */
+ function invalidateRow()
+ {
+ this.invoke = function invalidateRow_invoke()
+ {
+ // Fire 'TreeInvalidated' event by InvalidateRow()
+ var colCount = gTree.columns.count;
+ var column = gTree.columns.getFirstColumn();
+ while (column) {
+ gView.setCellText(1, column, "aloha 1x" + String(column.index));
+ column = column.getNext();
+ }
+
+ gTreeBox.invalidateRow(1);
+ }
+
+ this.eventSeq =
+ [
+ new nameChangeChecker("invalidateRow: ", 1, 0),
+ new nameChangeChecker("invalidateRow: ", 1, 1),
+ new rowNameChangeChecker("invalidateRow: ", 1),
+ new treeInvalidatedChecker("invalidateRow", 1, 1, null, null)
+ ];
+
+ this.getID = function invalidateRow_getID()
+ {
+ return "invalidate row";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ var gTree = null;
+ var gTreeBox = null;
+ var gTreeView = null;
+ var gQueue = null;
+
+ // gA11yEventDumpID = "debug";
+ gA11yEventDumpToConsole = true; // debuggin
+
+ function doTest()
+ {
+ // Initialize the tree
+ gTree = document.getElementById("tree");
+ gTreeBox = gTree.treeBoxObject;
+ gView = new nsTableTreeView(5);
+
+ // Perform actions
+ gQueue = new eventQueue();
+
+ gQueue.push(new setTreeView());
+ gQueue.push(new insertRow());
+ gQueue.push(new invalidateColumn());
+ gQueue.push(new invalidateRow());
+
+ gQueue.invoke();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=368835"
+ title="Fire TreeViewChanged/TreeRowCountChanged events.">
+ Mozilla Bug 368835
+ </a><br/>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=308564"
+ title="No accessibility events when data in a tree row changes.">
+ Mozilla Bug 308564
+ </a><br/>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=739524"
+ title="replace TreeViewChanged DOM event on direct call from XUL tree.">
+ Mozilla Bug 739524
+ </a><br/>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=743568"
+ title="Thunderbird message list tree emitting incorrect focus signals after message deleted.">
+ Mozilla Bug 743568
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="debug"/>
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col1" flex="1" primary="true" label="column"/>
+ <treecol id="col2" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren id="treechildren"/>
+ </tree>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/events/test_valuechange.html b/accessible/tests/mochitest/events/test_valuechange.html
new file mode 100644
index 000000000..3464ffdeb
--- /dev/null
+++ b/accessible/tests/mochitest/events/test_valuechange.html
@@ -0,0 +1,255 @@
+<html>
+
+<head>
+ <title>Accessible value change events testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript"
+ src="../value.js"></script>
+
+ <script type="application/javascript">
+ /**
+ * Do tests.
+ */
+ var gQueue = null;
+
+ // Value change invoker
+ function changeARIAValue(aNodeOrID, aValuenow, aValuetext)
+ {
+ this.DOMNode = getNode(aNodeOrID);
+ this.eventSeq = [ new invokerChecker(aValuetext ?
+ EVENT_TEXT_VALUE_CHANGE :
+ EVENT_VALUE_CHANGE, this.DOMNode)
+ ];
+
+ this.invoke = function changeARIAValue_invoke() {
+
+ // Note: this should not fire an EVENT_VALUE_CHANGE when aria-valuetext
+ // is not empty
+ if (aValuenow != undefined)
+ this.DOMNode.setAttribute("aria-valuenow", aValuenow);
+
+ // Note: this should always fire an EVENT_VALUE_CHANGE
+ if (aValuetext != undefined)
+ this.DOMNode.setAttribute("aria-valuetext", aValuetext);
+ }
+
+ this.check = function changeARIAValue_check() {
+ var acc = getAccessible(aNodeOrID, [nsIAccessibleValue]);
+ if (!acc)
+ return;
+
+ // Note: always test against valuetext first because the existence of
+ // aria-valuetext takes precedence over aria-valuenow in gecko.
+ is(acc.value, (aValuetext != undefined)? aValuetext : aValuenow,
+ "Wrong value of " + prettyName(aNodeOrID));
+ }
+
+ this.getID = function changeARIAValue_getID() {
+ return prettyName(aNodeOrID) + " value changed";
+ }
+ }
+
+ function changeValue(aID, aValue)
+ {
+ this.DOMNode = getNode(aID);
+ this.eventSeq = [new invokerChecker(EVENT_TEXT_VALUE_CHANGE,
+ this.DOMNode)
+ ];
+
+ this.invoke = function changeValue_invoke()
+ {
+ this.DOMNode.value = aValue;
+ }
+
+ this.check = function changeValue_check()
+ {
+ var acc = getAccessible(this.DOMNode);
+ is(acc.value, aValue, "Wrong value for " + prettyName(aID));
+ }
+
+ this.getID = function changeValue_getID()
+ {
+ return prettyName(aID) + " value changed";
+ }
+ }
+
+ function changeProgressValue(aID, aValue)
+ {
+ this.DOMNode = getNode(aID);
+ this.eventSeq = [new invokerChecker(EVENT_VALUE_CHANGE, this.DOMNode)];
+
+ this.invoke = function changeProgressValue_invoke()
+ {
+ this.DOMNode.value = aValue;
+ }
+
+ this.check = function changeProgressValue_check()
+ {
+ var acc = getAccessible(this.DOMNode);
+ is(acc.value, aValue+"%", "Wrong value for " + prettyName(aID));
+ }
+
+ this.getID = function changeProgressValue_getID()
+ {
+ return prettyName(aID) + " value changed";
+ }
+ }
+
+ function changeRangeValue(aID)
+ {
+ this.DOMNode = getNode(aID);
+ this.eventSeq = [new invokerChecker(EVENT_VALUE_CHANGE, this.DOMNode)];
+
+ this.invoke = function changeRangeValue_invoke()
+ {
+ synthesizeMouse(getNode(aID), 5, 5, { });
+ }
+
+ this.finalCheck = function changeRangeValue_finalCheck()
+ {
+ var acc = getAccessible(this.DOMNode);
+ is(acc.value, "0", "Wrong value for " + prettyName(aID));
+ }
+
+ this.getID = function changeRangeValue_getID()
+ {
+ return prettyName(aID) + " range value changed";
+ }
+ }
+
+ function changeSelectValue(aID, aKey, aValue)
+ {
+ this.eventSeq =
+ [ new invokerChecker(EVENT_TEXT_VALUE_CHANGE, getAccessible(aID)) ];
+
+ this.invoke = function changeSelectValue_invoke()
+ {
+ getNode(aID).focus();
+ synthesizeKey(aKey, {}, window);
+ }
+
+ this.finalCheck = function changeSelectValue_finalCheck()
+ {
+ is(getAccessible(aID).value, aValue, "Wrong value for " + prettyName(aID));
+ }
+
+ this.getID = function changeSelectValue_getID()
+ {
+ return `${prettyName(aID)} closed select value change on '${aKey}'' key press`;
+ }
+ }
+
+ //enableLogging("DOMEvents");
+ //gA11yEventDumpToConsole = true;
+ function doTests()
+ {
+ // Test initial values
+ testValue("slider_vn", "5", 5, 0, 1000, 0);
+ testValue("slider_vnvt", "plain", 0, 0, 5, 0);
+ testValue("slider_vt", "hi", 0, 0, 3, 0);
+ testValue("scrollbar", "5", 5, 0, 1000, 0);
+ testValue("progress", "22%", 22, 0, 100, 0);
+ testValue("range", "6", 6, 0, 10, 1);
+
+ // Test value change events
+ gQueue = new eventQueue();
+
+ gQueue.push(new changeARIAValue("slider_vn", "6", undefined));
+ gQueue.push(new changeARIAValue("slider_vt", undefined, "hey!"));
+ gQueue.push(new changeARIAValue("slider_vnvt", "3", "sweet"));
+ gQueue.push(new changeARIAValue("scrollbar", "6", undefined));
+
+ gQueue.push(new changeValue("combobox", "hello"));
+
+ gQueue.push(new changeProgressValue("progress", "50"));
+ gQueue.push(new changeRangeValue("range"));
+
+ gQueue.push(new changeSelectValue("select", "VK_DOWN", "2nd"));
+ gQueue.push(new changeSelectValue("select", "3", "3rd"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=478032"
+ title=" Fire delayed value changed event for aria-valuetext changes">
+ Mozilla Bug 478032
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=529289"
+ title="We dont expose new aria role 'scrollbar' and property aria-orientation">
+ Mozilla Bug 529289
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=559764"
+ title="Make HTML5 input@type=range element accessible">
+ Mozilla Bug 559764
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=703202"
+ title="ARIA comboboxes don't fire value change events">
+ Mozilla Bug 703202
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=761901"
+ title=" HTML5 progress accessible should fire value change event">
+ Mozilla Bug 761901
+ </a>
+
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <div id="eventdump"></div>
+
+ <!-- ARIA sliders -->
+ <div id="slider_vn" role="slider" aria-valuenow="5"
+ aria-valuemin="0" aria-valuemax="1000">slider</div>
+
+ <div id="slider_vt" role="slider" aria-valuetext="hi"
+ aria-valuemin="0" aria-valuemax="3">greeting slider</div>
+
+ <div id="slider_vnvt" role="slider" aria-valuenow="0" aria-valuetext="plain"
+ aria-valuemin="0" aria-valuemax="5">sweetness slider</div>
+
+ <!-- ARIA scrollbar -->
+ <div id="scrollbar" role="scrollbar" aria-valuenow="5"
+ aria-valuemin="0" aria-valuemax="1000">slider</div>
+
+ <!-- ARIA combobox -->
+ <input id="combobox" role="combobox" aria-autocomplete="inline">
+
+ <!-- progress bar -->
+ <progress id="progress" value="22" max="100"></progress>
+
+ <!-- input@type="range" -->
+ <input type="range" id="range" min="0" max="10" value="6">
+
+ <select id="select">
+ <option>1st</option>
+ <option>2nd</option>
+ <option>3rd</option>
+ </select>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/focus/a11y.ini b/accessible/tests/mochitest/focus/a11y.ini
new file mode 100644
index 000000000..48d5e6654
--- /dev/null
+++ b/accessible/tests/mochitest/focus/a11y.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+
+[test_focusedChild.html]
+skip-if = (os == 'win' && (os_version == '6.2' || os_version == '6.3')) # bug 845134
+[test_takeFocus.html]
+skip-if = buildapp == 'mulet'
+[test_takeFocus.xul]
diff --git a/accessible/tests/mochitest/focus/test_focusedChild.html b/accessible/tests/mochitest/focus/test_focusedChild.html
new file mode 100644
index 000000000..e03e0469c
--- /dev/null
+++ b/accessible/tests/mochitest/focus/test_focusedChild.html
@@ -0,0 +1,87 @@
+<html>
+
+<head>
+ <title>nsIAccessible::focusedChild testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function openWnd()
+ {
+ this.eventSeq = [ new invokerChecker(EVENT_FOCUS,
+ getDialogAccessible,
+ this) ];
+
+ this.invoke = function openWnd_invoke()
+ {
+ this.dialog = window.openDialog("about:mozilla",
+ "AboutMozilla",
+ "chrome,width=600,height=600");
+ }
+
+ this.finalCheck = function openWnd_finalCheck()
+ {
+ var app = getApplicationAccessible();
+ is(app.focusedChild, getDialogAccessible(this),
+ "Wrong focused child");
+
+ this.dialog.close();
+ }
+
+ this.getID = function openWnd_getID()
+ {
+ return "focusedChild for application accessible";
+ }
+
+ function getDialogAccessible(aInvoker)
+ {
+ return getAccessible(aInvoker.dialog.document);
+ }
+ }
+
+ gA11yEventDumpToConsole = true;
+ var gQueue = null;
+
+ function doTest()
+ {
+ enableLogging("focus,doclifecycle");
+ gQueue = new eventQueue();
+
+ gQueue.push(new openWnd());
+
+ gQueue.onFinish = function() { disableLogging(); }
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=677467"
+ title="focusedChild crashes on application accessible">
+ Mozilla Bug 677467
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/focus/test_takeFocus.html b/accessible/tests/mochitest/focus/test_takeFocus.html
new file mode 100644
index 000000000..23938c819
--- /dev/null
+++ b/accessible/tests/mochitest/focus/test_takeFocus.html
@@ -0,0 +1,128 @@
+<html>
+
+<head>
+ <title>nsIAccessible::takeFocus testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function takeFocusInvoker(aID)
+ {
+ this.accessible = getAccessible(aID);
+
+ this.eventSeq = [ new focusChecker(this.accessible) ];
+
+ this.invoke = function takeFocusInvoker_invoke()
+ {
+ this.accessible.takeFocus();
+ }
+
+ this.getID = function takeFocusInvoker_getID()
+ {
+ return "takeFocus for " + prettyName(aID);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTest()
+ {
+ disableLogging(); // from test_focusedChild
+ gQueue = new eventQueue();
+
+ gQueue.push(new takeFocusInvoker("aria-link"));
+ gQueue.push(new takeFocusInvoker("aria-link2"));
+ gQueue.push(new takeFocusInvoker("link"));
+ gQueue.push(new takeFocusInvoker("item2"));
+ gQueue.push(new takeFocusInvoker("plugin"));
+ gQueue.push(new takeFocusInvoker(document));
+ gQueue.push(new takeFocusInvoker("lb_item2"));
+ gQueue.push(new takeFocusInvoker(document));
+ gQueue.push(new takeFocusInvoker("lb_item3.2"));
+ gQueue.push(new takeFocusInvoker(document));
+ gQueue.push(new takeFocusInvoker("lb_item3.1"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ function waitForPlugin()
+ {
+ window.setTimeout((isAccessible("plugin") ? doTest : waitForPlugin), 0);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
+ addA11yLoadEvent(waitForPlugin);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=429547"
+ title="Support aria-activedescendant usage in nsIAccesible::TakeFocus()">
+ Mozilla Bug 429547
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=452710"
+ title="nsIAccessible::takeFocus testing">
+ Mozilla Bug 452710
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=646361"
+ title="No focus event fired on document when focus is set to the document while focused in a plugin">
+ Mozilla Bug 646361
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=706067"
+ title="Make takeFocus work on widget items">
+ Mozilla Bug 706067
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <span id="aria-link" role="link" tabindex="0">link</span>
+ <span id="aria-link2" role="link" tabindex="0">link</span>
+
+ <a id="link" href="">link</a>
+
+ <div role="listbox" aria-activedescendant="item1" id="container" tabindex="1">
+ <div role="option" id="item1">item1</div>
+ <div role="option" id="item2">item2</div>
+ <div role="option" id="item3">item3</div>
+ </div>
+
+ <embed id="plugin" type="application/x-test" width="200" height="200" wmode="window"></embed>
+
+ <select id="listbox" size="5">
+ <option id="lb_item1">item1</option>
+ <option id="lb_item2">item2</option>
+ <optgroup>
+ <option id="lb_item3.1">item 3.1</option>
+ <option id="lb_item3.2">item 3.2</option>
+ </optgroup>
+ </select>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/focus/test_takeFocus.xul b/accessible/tests/mochitest/focus/test_takeFocus.xul
new file mode 100644
index 000000000..11f6e2a28
--- /dev/null
+++ b/accessible/tests/mochitest/focus/test_takeFocus.xul
@@ -0,0 +1,106 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible focus testing">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function takeFocusInvoker(aID, aArgConverterFunc)
+ {
+ this.targetFunc = aArgConverterFunc ? aArgConverterFunc : getAccessible;
+
+ this.eventSeq = [ new focusChecker(this.targetFunc, aID) ];
+
+ this.invoke = function takeFocusInvoker_invoke()
+ {
+ this.targetFunc.call(null, aID).takeFocus();
+ }
+
+ this.getID = function takeFocusInvoker_getID()
+ {
+ return "takeFocus for " + prettyName(aID);
+ }
+ }
+
+ function getLastChild(aID)
+ {
+ return getAccessible(aID).lastChild;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Tests
+
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTests()
+ {
+ // Test focus events.
+ gQueue = new eventQueue();
+
+ gQueue.push(new takeFocusInvoker("tree", getLastChild));
+ gQueue.push(new takeFocusInvoker("listitem2"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yXULTreeLoadEvent(doTests, "tree", new nsTableTreeView(5));
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=706067"
+ title="Make takeFocus work on widget items">
+ Mozilla Bug 706067
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col1" flex="1" primary="true" label="column"/>
+ <treecol id="col2" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren id="treechildren"/>
+ </tree>
+
+ <listbox id="listbox">
+ <listitem id="listitem1">item1</listitem>
+ <listitem id="listitem2">item2</listitem>
+ </listbox>
+
+ <vbox id="eventdump"/>
+ </vbox>
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/formimage.png b/accessible/tests/mochitest/formimage.png
new file mode 100644
index 000000000..10e44bf92
--- /dev/null
+++ b/accessible/tests/mochitest/formimage.png
Binary files differ
diff --git a/accessible/tests/mochitest/grid.js b/accessible/tests/mochitest/grid.js
new file mode 100644
index 000000000..56f2412ff
--- /dev/null
+++ b/accessible/tests/mochitest/grid.js
@@ -0,0 +1,149 @@
+const nsIDOMKeyEvent = Components.interfaces.nsIDOMKeyEvent;
+
+/**
+ * Create grid object based on HTML table.
+ */
+function grid(aTableIdentifier)
+{
+ this.getRowCount = function getRowCount()
+ {
+ return this.table.rows.length - (this.table.tHead ? 1 : 0);
+ }
+ this.getColsCount = function getColsCount()
+ {
+ return this.table.rows[0].cells.length;
+ }
+
+ this.getRowAtIndex = function getRowAtIndex(aIndex)
+ {
+ return this.table.rows[this.table.tHead ? aIndex + 1 : aIndex];
+ }
+
+ this.getMaxIndex = function getMaxIndex()
+ {
+ return this.getRowCount() * this.getColsCount() - 1;
+ }
+
+ this.getCellAtIndex = function getCellAtIndex(aIndex)
+ {
+ var rowCount = this.getRowCount();
+ var colsCount = this.getColsCount();
+
+ var rowIdx = Math.floor(aIndex / colsCount);
+ var colIdx = aIndex % colsCount;
+
+ var row = this.getRowAtIndex(rowIdx);
+ return row.cells[colIdx];
+ }
+
+ this.getIndexByCell = function getIndexByCell(aCell)
+ {
+ var colIdx = aCell.cellIndex;
+
+ var rowIdx = aCell.parentNode.rowIndex;
+ if (this.table.tHead)
+ rowIdx -= 1;
+
+ var colsCount = this.getColsCount();
+ return rowIdx * colsCount + colIdx;
+ }
+
+ this.getCurrentCell = function getCurrentCell()
+ {
+ var rowCount = this.table.rows.length;
+ var colsCount = this.getColsCount();
+ for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ for (var colIdx = 0; colIdx < colsCount; colIdx++) {
+ var cell = this.table.rows[rowIdx].cells[colIdx];
+ if (cell.hasAttribute("tabindex"))
+ return cell;
+ }
+ }
+ return null;
+ }
+
+ this.initGrid = function initGrid()
+ {
+ this.table.addEventListener("keypress", this, false);
+ this.table.addEventListener("click", this, false);
+ }
+
+ this.handleEvent = function handleEvent(aEvent)
+ {
+ if (aEvent instanceof nsIDOMKeyEvent)
+ this.handleKeyEvent(aEvent);
+ else
+ this.handleClickEvent(aEvent);
+ }
+
+ this.handleKeyEvent = function handleKeyEvent(aEvent)
+ {
+ if (aEvent.target.localName != "td")
+ return;
+
+ var cell = aEvent.target;
+ switch(aEvent.keyCode) {
+ case nsIDOMKeyEvent.DOM_VK_UP:
+ var colsCount = this.getColsCount();
+ var idx = this.getIndexByCell(cell);
+ var upidx = idx - colsCount;
+ if (upidx >= 0) {
+ cell.removeAttribute("tabindex");
+ var upcell = this.getCellAtIndex(upidx);
+ upcell.setAttribute("tabindex", "0");
+ upcell.focus();
+ }
+ break;
+
+ case nsIDOMKeyEvent.DOM_VK_DOWN:
+ var colsCount = this.getColsCount();
+ var idx = this.getIndexByCell(cell);
+ var downidx = idx + colsCount;
+ if (downidx <= this.getMaxIndex()) {
+ cell.removeAttribute("tabindex");
+ var downcell = this.getCellAtIndex(downidx);
+ downcell.setAttribute("tabindex", "0");
+ downcell.focus();
+ }
+ break;
+
+ case nsIDOMKeyEvent.DOM_VK_LEFT:
+ var idx = this.getIndexByCell(cell);
+ if (idx > 0) {
+ cell.removeAttribute("tabindex");
+ var prevcell = this.getCellAtIndex(idx - 1);
+ prevcell.setAttribute("tabindex", "0");
+ prevcell.focus();
+ }
+ break;
+
+ case nsIDOMKeyEvent.DOM_VK_RIGHT:
+ var idx = this.getIndexByCell(cell);
+ if (idx < this.getMaxIndex()) {
+ cell.removeAttribute("tabindex");
+ var nextcell = this.getCellAtIndex(idx + 1);
+ nextcell.setAttribute("tabindex", "0");
+ nextcell.focus();
+ }
+ break;
+ }
+ }
+
+ this.handleClickEvent = function handleClickEvent(aEvent)
+ {
+ if (aEvent.target.localName != "td")
+ return;
+
+ var curCell = this.getCurrentCell();
+ var cell = aEvent.target;
+
+ if (cell != curCell) {
+ curCell.removeAttribute("tabindex");
+ cell.setAttribute("tabindex", "0");
+ cell.focus();
+ }
+ }
+
+ this.table = getNode(aTableIdentifier);
+ this.initGrid();
+}
diff --git a/accessible/tests/mochitest/hittest/a11y.ini b/accessible/tests/mochitest/hittest/a11y.ini
new file mode 100644
index 000000000..13bca54de
--- /dev/null
+++ b/accessible/tests/mochitest/hittest/a11y.ini
@@ -0,0 +1,14 @@
+[DEFAULT]
+support-files = zoom_tree.xul
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/letters.gif
+
+[test_browser.html]
+[test_canvas_hitregion.html]
+skip-if = (os == "android" || appname == "b2g")
+[test_general.html]
+[test_menu.xul]
+[test_shadowroot.html]
+[test_zoom.html]
+[test_zoom_text.html]
+[test_zoom_tree.xul]
diff --git a/accessible/tests/mochitest/hittest/test_browser.html b/accessible/tests/mochitest/hittest/test_browser.html
new file mode 100644
index 000000000..75e0b9a95
--- /dev/null
+++ b/accessible/tests/mochitest/hittest/test_browser.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>nsIAccessible::childAtPoint() from browser tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../layout.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ // Hit testing. See bug #726097
+ getNode("hittest").scrollIntoView(true);
+
+ var hititem = getAccessible("hititem");
+ var hittest = getAccessible("hittest");
+
+ var [hitX, hitY, hitWidth, hitHeight] = getBounds(hititem);
+ var tgtX = hitX + hitWidth / 2;
+ var tgtY = hitY + hitHeight / 2;
+
+ var rootAcc = getRootAccessible();
+ var docAcc = getAccessible(document);
+ var outerDocAcc = docAcc.parent;
+
+ var hitAcc = rootAcc.getDeepestChildAtPoint(tgtX, tgtY);
+ is(hitAcc, hititem, "Hit match at " + tgtX + "," + tgtY +
+ ". Found: " + prettyName(hitAcc));
+ var hitAcc2 = docAcc.getDeepestChildAtPoint(tgtX, tgtY);
+ is(hitAcc, hitAcc2, "Hit match at " + tgtX + "," + tgtY +
+ ". Found: " + prettyName(hitAcc2));
+
+ hitAcc = outerDocAcc.getChildAtPoint(tgtX, tgtY);
+ is(hitAcc, docAcc, "Hit match at " + tgtX + "," + tgtY +
+ ". Found: " + prettyName(hitAcc));
+ hitAcc = docAcc.getChildAtPoint(tgtX, tgtY);
+ is(hitAcc, hittest, "Hit match at " + tgtX + "," + tgtY +
+ ". Found: " + prettyName(hitAcc));
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=726097"
+ title="nsIAccessible::childAtPoint() from browser tests">Mozilla Bug 726097</a>
+
+ <div id="hittest">
+ <div id="hititem"><span role="image">img</span>item</div>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/hittest/test_canvas_hitregion.html b/accessible/tests/mochitest/hittest/test_canvas_hitregion.html
new file mode 100644
index 000000000..fc1df3d60
--- /dev/null
+++ b/accessible/tests/mochitest/hittest/test_canvas_hitregion.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>nsIAccessible::childAtPoint() for canvas from browser tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../layout.js"></script>
+
+ <script type="application/javascript">
+ function redrawCheckbox(context, element, x, y)
+ {
+ context.save();
+ context.font = '10px sans-serif';
+ context.textAlign = 'left';
+ context.textBaseline = 'middle';
+ var metrics = context.measureText(element.parentNode.textContent);
+ context.beginPath();
+ context.strokeStyle = 'black';
+ context.rect(x-5, y-5, 10, 10);
+ context.stroke();
+ if (element.checked) {
+ context.fillStyle = 'black';
+ context.fill();
+ }
+ context.fillText(element.parentNode.textContent, x+5, y);
+
+ context.beginPath();
+ context.rect(x-7, y-7, 12 + metrics.width+2, 14);
+
+ if (document.activeElement == element)
+ context.drawFocusIfNeeded(element);
+ context.addHitRegion({control: element});
+ context.restore();
+ }
+
+ function doTest()
+ {
+ var offsetX = 20, offsetY = 40;
+ getNode("hitcanvas").scrollIntoView(true);
+
+ var context = document.getElementById("hitcanvas").getContext('2d');
+ redrawCheckbox(context, document.getElementById('hitcheck'),
+ offsetX, offsetY);
+
+ var hitcanvas = getAccessible("hitcanvas");
+ var hitcheck = getAccessible("hitcheck");
+
+ var [hitX, hitY, hitWidth, hitHeight] = getBounds(hitcanvas);
+ var [deltaX, deltaY] = CSSToDevicePixels(window, offsetX, offsetY);
+
+ var docAcc = getAccessible(document);
+
+ // test if we hit the region associated with the shadow dom checkbox
+ var tgtX = hitX + deltaX;
+ var tgtY = hitY + deltaY;
+ hitAcc = docAcc.getDeepestChildAtPoint(tgtX, tgtY);
+ isObject(hitAcc, hitcheck, `Hit match at (${tgtX}, ${tgtY}`);
+
+ // test that we don't hit the region associated with the shadow dom checkbox
+ tgtY = hitY + deltaY * 2;
+ hitAcc = docAcc.getDeepestChildAtPoint(tgtX, tgtY);
+ isObject(hitAcc, hitcanvas, `Hit match at (${tgtX}, ${tgtY}`);
+
+ SimpleTest.finish();
+ }
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(function() {
+ SpecialPowers.pushPrefEnv({"set": [['canvas.hitregions.enabled', true]]}, doTest);
+ });
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=966591"
+ title="nsIAccessible::childAtPoint() for canvas hit regions from browser tests">Mozilla Bug 966591</a>
+
+ <canvas id="hitcanvas">
+ <input id="hitcheck" type="checkbox"><label for="showA"> Show A </label>
+ </canvas>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/hittest/test_general.html b/accessible/tests/mochitest/hittest/test_general.html
new file mode 100644
index 000000000..74ff4fe29
--- /dev/null
+++ b/accessible/tests/mochitest/hittest/test_general.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>nsIAccessible::childAtPoint() tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../layout.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function doPreTest()
+ {
+ waitForImageMap("imgmap", doTest);
+ }
+
+ function doTest()
+ {
+ // Not specific case, child and deepchild testing.
+ var list = getAccessible("list");
+ var listitem = getAccessible("listitem");
+ var image = getAccessible("image");
+if (!MAC) {
+ testChildAtPoint(list, 1, 1, listitem, image.firstChild);
+} else {
+ todo(false, "Bug 746974 - children must match on all platforms, disable failing test on Mac");
+}
+
+ // ::MustPrune case (in this case childAtPoint doesn't look inside a
+ // textbox), point is inside of textbox.
+ var txt = getAccessible("txt");
+ testChildAtPoint(txt, 1, 1, txt, txt);
+
+ // ::MustPrune case, point is outside of textbox accessible but is in
+ // document.
+ testChildAtPoint(txt, -1, 1, null, null);
+
+ // ::MustPrune case, point is outside of root accessible.
+ testChildAtPoint(txt, -10000, 10000, null, null);
+
+ // Not specific case, point is inside of btn accessible.
+ var btn = getAccessible("btn");
+ var btnText = btn.firstChild;
+ testChildAtPoint(btn, 1, 1, btn, btn);
+
+ // Not specific case, point is outside of btn accessible.
+ testChildAtPoint(btn, -1, 1, null, null);
+
+ // Out of flow accessible testing, do not return out of flow accessible
+ // because it's not a child of the accessible even visually it is.
+ var rectArea = getNode("area").getBoundingClientRect();
+ var outOfFlow = getNode("outofflow");
+ outOfFlow.style.left = rectArea.left + "px";
+ outOfFlow.style.top = rectArea.top + "px";
+
+ testChildAtPoint("area", 1, 1, "area", "area");
+
+ // Test image maps. Their children are not in the layout tree.
+ var theLetterA = getAccessible("imgmap").firstChild;
+ hitTest("imgmap", theLetterA, theLetterA);
+ hitTest("container", "imgmap", theLetterA);
+
+ // hit testing for element contained by zero-width element
+ hitTest("container2", "container2_input", "container2_input");
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doPreTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=491657"
+ title="nsIAccessible::childAtPoint() tests">Mozilla Bug 491657</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div role="list" id="list">
+ <div role="listitem" id="listitem"><span role="image" id="image">img</span>item</div>
+ </div>
+
+ <span role="button">button1</span><span role="button" id="btn">button2</span>
+
+ <span role="textbox">textbox1</span><span role="textbox" id="txt">textbox2</span>
+
+ <div id="outofflow" style="width: 10px; height: 10px; position: absolute; left: 0px; top: 0px; background-color: yellow;">
+ </div>
+ <div id="area" style="width: 100px; height: 100px; background-color: blue;"></div>
+
+ <map name="atoz_map">
+ <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#a"
+ coords="0,0,15,15" alt="thelettera" shape="rect"/>
+ </map>
+
+ <div id="container">
+ <img id="imgmap" width="447" height="15" usemap="#atoz_map" src="../letters.gif"/>
+ </div>
+
+ <div id="container2" style="width: 0px">
+ <input id="container2_input">
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/hittest/test_menu.xul b/accessible/tests/mochitest/hittest/test_menu.xul
new file mode 100644
index 000000000..30bf93acf
--- /dev/null
+++ b/accessible/tests/mochitest/hittest/test_menu.xul
@@ -0,0 +1,134 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Hit testing for XUL menus">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../layout.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function openMenu(aMenuID, aMenuPopupID, aMenuItemID)
+ {
+ this.menuNode = getNode(aMenuID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, this.menuNode)
+ ];
+
+ this.invoke = function openMenu_invoke()
+ {
+ this.menuNode.open = true;
+ }
+
+ this.finalCheck = function openMenu_finalCheck()
+ {
+ hitTest(aMenuPopupID, aMenuItemID, aMenuItemID);
+ }
+
+ this.getID = function openMenu_invoke()
+ {
+ return "open menu '" + aMenuID + "' and do hit testing";
+ }
+ }
+
+ function closeMenu(aID, aSubID, aSub2ID)
+ {
+ this.menuNode = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, document)
+ ];
+
+ this.invoke = function openMenu_invoke()
+ {
+ this.menuNode.open = false;
+ }
+
+ this.finalCheck = function openMenu_finalCheck()
+ {
+ testStates(aID, 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN);
+ testStates(aSubID, STATE_INVISIBLE, 0, STATE_OFFSCREEN);
+ testStates(aSub2ID, STATE_INVISIBLE, 0, STATE_OFFSCREEN);
+ }
+
+ this.getID = function openMenu_invoke()
+ {
+ return "open menu and test states";
+ }
+ }
+
+ var gQueue = null;
+ function doTest()
+ {
+ if (LINUX) {
+ ok(true, "No tests is running on Linux");
+ SimpleTest.finish();
+ return;
+ }
+
+ getNode("mi_file1").scrollIntoView(true);
+
+ gQueue = new eventQueue();
+ gQueue.push(new openMenu("mi_file1", "mp_file1", "mi_file1.1"));
+ gQueue.push(new openMenu("mi_file1.2", "mp_file1.2", "mi_file1.2.1"));
+ gQueue.push(new closeMenu("mi_file1", "mi_file1.1", "mi_file1.2.1"));
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=670087"
+ title="AccessibleObjectFromPoint returns incorrect accessible for popup menus">
+ Bug 670087
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+
+ <menubar>
+ <menu label="File" id="mi_file1">
+ <menupopup id="mp_file1">
+ <menuitem label="SubFile" id="mi_file1.1"/>
+ <menu label="SubFile2" id="mi_file1.2">
+ <menupopup style="max-height: 5em;" id="mp_file1.2">
+ <menuitem label="SubSubFile" id="mi_file1.2.1"/>
+ <menuitem label="SubSubFile2" id="mi_file1.2.2"/>
+ <menuitem label="SubSubFile3" id="mi_file1.2.3"/>
+ <menuitem label="SubSubFile4" id="mi_file1.2.4"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+ </menu>
+ </menubar>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/hittest/test_shadowroot.html b/accessible/tests/mochitest/hittest/test_shadowroot.html
new file mode 100644
index 000000000..41400840d
--- /dev/null
+++ b/accessible/tests/mochitest/hittest/test_shadowroot.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>ShadowRoot hit tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../layout.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ var componentAcc = getAccessible('component1');
+ testChildAtPoint(componentAcc, 1, 1, componentAcc.firstChild,
+ componentAcc.firstChild);
+
+ componentAcc = getAccessible('component2');
+ testChildAtPoint(componentAcc, 1, 1, componentAcc.firstChild,
+ componentAcc.firstChild);
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Test getChildAtPoint works for shadow DOM content"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1027315">
+ Mozilla Bug 1027315
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div role="group" class="components" id="component1" style="display: inline-block;">
+ <!--
+ <div role="button" id="component-child"
+ style="width: 100px; height: 100px; background-color: pink;">
+ </div>
+ -->
+ </div>
+ <div role="group" class="components" id="component2" style="display: inline-block;">
+ <!--
+ <button>Hello world</button>
+ -->
+ </div>
+ <script>
+ // This routine adds the comment children of each 'component' to its
+ // shadow root.
+ var components = document.querySelectorAll('.components');
+ for (var i = 0; i < components.length; i++) {
+ var component = components[i];
+ var shadow = component.createShadowRoot();
+ for (var child = component.firstChild; child; child = child.nextSibling) {
+ if (child.nodeType === 8)
+ shadow.innerHTML = child.data;
+ }
+ }
+ </script>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/hittest/test_zoom.html b/accessible/tests/mochitest/hittest/test_zoom.html
new file mode 100644
index 000000000..bc1cf9d55
--- /dev/null
+++ b/accessible/tests/mochitest/hittest/test_zoom.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>childAtPoint when page is zoomed</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../layout.js"></script>
+ <script type="application/javascript"
+ src="../browser.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+if (!MAC) {
+ var tabDocument = currentTabDocument();
+ var p1 = tabDocument.body.firstElementChild;
+ var p2 = tabDocument.body.lastElementChild;
+
+ hitTest(tabDocument, p1, p1.firstChild);
+ hitTest(tabDocument, p2, p2.firstChild);
+
+ zoomDocument(tabDocument, 2.0);
+
+ hitTest(tabDocument, p1, p1.firstChild);
+ hitTest(tabDocument, p2, p2.firstChild);
+
+ closeBrowserWindow();
+} else {
+ todo(false, "Bug 746974 - deepest child must be correct on all platforms, disabling on Mac!");
+}
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ openBrowserWindow(doTest,
+ "data:text/html,<html><body><p>para 1</p><p>para 2</p></body></html>",
+ { left: 100, top: 100 });
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=727942"
+ title="childAtPoint may return incorrect accessibles when page zoomed">
+ Mozilla Bug 727942
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/hittest/test_zoom_text.html b/accessible/tests/mochitest/hittest/test_zoom_text.html
new file mode 100644
index 000000000..3a75296b2
--- /dev/null
+++ b/accessible/tests/mochitest/hittest/test_zoom_text.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>getOffsetAtPoint when page is zoomed</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../layout.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ var hyperText = getNode("paragraph");
+ var textNode = hyperText.firstChild;
+ var [x, y, width, height] = getBounds(textNode);
+ testOffsetAtPoint(hyperText, x + width / 2, y + height / 2,
+ COORDTYPE_SCREEN_RELATIVE,
+ hyperText.textContent.length / 2);
+
+ zoomDocument(document, 2.0);
+
+ var [x, y, width, height] = getBounds(textNode);
+ testOffsetAtPoint(hyperText, x + width / 2, y + height / 2,
+ COORDTYPE_SCREEN_RELATIVE,
+ hyperText.textContent.length / 2);
+
+ zoomDocument(document, 1.0);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=727942"
+ title="getOffsetAtPoint returns incorrect value when page is zoomed">
+ Mozilla Bug 727942
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <p id="paragraph" style="font-family: monospace;">Болтали две сороки</p>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/hittest/test_zoom_tree.xul b/accessible/tests/mochitest/hittest/test_zoom_tree.xul
new file mode 100644
index 000000000..da1628df2
--- /dev/null
+++ b/accessible/tests/mochitest/hittest/test_zoom_tree.xul
@@ -0,0 +1,100 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="nsIAccessible::getChildAtPoint and getDeepestChildAtPoint">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/chrome-harness.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../layout.js" />
+ <script type="application/javascript"
+ src="../browser.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function doTest()
+ {
+ var tabDocument = currentTabDocument();
+ var tabWindow = currentTabWindow();
+
+ var tree = tabDocument.getElementById("tree");
+ var treecols = tabDocument.getElementById("treecols");
+ var treecol1 = tabDocument.getElementById("treecol1");
+
+ // tree columns
+ hitTest(tree, treecols, treecol1);
+
+ // tree rows and cells
+ var treeBoxObject = tree.treeBoxObject;
+ var treeBodyBoxObj = tree.treeBoxObject.treeBody.boxObject;
+ var rect = treeBoxObject.getCoordsForCellItem(1, tree.columns[0], "cell");
+
+ var treeAcc = getAccessible(tree, [nsIAccessibleTable]);
+ var cellAcc = treeAcc.getCellAt(1, 0);
+ var rowAcc = cellAcc.parent;
+
+ var cssX = rect.x + treeBodyBoxObj.x;
+ var cssY = rect.y + treeBodyBoxObj.y;
+ var [x, y] = CSSToDevicePixels(tabWindow, cssX, cssY);
+
+ testChildAtPoint(treeAcc, x, y, rowAcc, cellAcc);
+ testChildAtPoint(rowAcc, x, y, cellAcc, cellAcc);
+
+ // do zoom
+ zoomDocument(tabDocument, 1.5);
+
+ // tree columns
+ hitTest(tree, treecols, treecol1);
+
+ // tree rows and cells
+ var [x, y] = CSSToDevicePixels(tabWindow, cssX, cssY);
+ testChildAtPoint(treeAcc, x, y, rowAcc, cellAcc);
+ testChildAtPoint(rowAcc, x, y, cellAcc, cellAcc);
+
+ closeBrowserWindow();
+ SimpleTest.finish();
+ }
+
+ function prepareTest()
+ {
+ var tabDocument = currentTabDocument();
+ var tree = tabDocument.getElementById("tree");
+ loadXULTreeAndDoTest(doTest, tree, new nsTableTreeView(5));
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ openBrowserWindow(prepareTest,
+ getRootDirectory(window.location.href) + "zoom_tree.xul",
+ { left: 100, top: 100 });
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=471493"
+ title=" crash [@ nsPropertyTable::GetPropertyInternal]">
+ Mozilla Bug 471493
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+ </hbox>
+
+</window>
diff --git a/accessible/tests/mochitest/hittest/zoom_tree.xul b/accessible/tests/mochitest/hittest/zoom_tree.xul
new file mode 100644
index 000000000..52ec0932a
--- /dev/null
+++ b/accessible/tests/mochitest/hittest/zoom_tree.xul
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="nsIAccessible::getChildAtPoint and getDeepestChildAtPoint for XUL trees">
+
+ <tree id="tree" flex="1">
+ <treecols id="treecols">
+ <treecol id="treecol1" flex="1" primary="true" label="column"/>
+ <treecol id="treecol2" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren id="treechildren"/>
+ </tree>
+
+</window>
+
diff --git a/accessible/tests/mochitest/hyperlink/a11y.ini b/accessible/tests/mochitest/hyperlink/a11y.ini
new file mode 100644
index 000000000..f37dca8e1
--- /dev/null
+++ b/accessible/tests/mochitest/hyperlink/a11y.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+support-files = hyperlink.js
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/letters.gif
+
+[test_general.html]
+[test_general.xul]
diff --git a/accessible/tests/mochitest/hyperlink/hyperlink.js b/accessible/tests/mochitest/hyperlink/hyperlink.js
new file mode 100644
index 000000000..4daa16275
--- /dev/null
+++ b/accessible/tests/mochitest/hyperlink/hyperlink.js
@@ -0,0 +1,42 @@
+/**
+ * Focus hyperlink invoker.
+ *
+ * @param aID [in] hyperlink identifier
+ * @param aSelectedAfter [in] specifies if hyperlink is selected/focused after
+ * the focus
+ */
+function focusLink(aID, aSelectedAfter)
+{
+ this.node = getNode(aID);
+ this.accessible = getAccessible(this.node);
+
+ this.eventSeq = [];
+ this.unexpectedEventSeq = [];
+
+ var checker = new invokerChecker(EVENT_FOCUS, this.accessible);
+ if (aSelectedAfter)
+ this.eventSeq.push(checker);
+ else
+ this.unexpectedEventSeq.push(checker);
+
+ this.invoke = function focusLink_invoke()
+ {
+ var expectedStates = (aSelectedAfter ? STATE_FOCUSABLE : 0);
+ var unexpectedStates = (!aSelectedAfter ? STATE_FOCUSABLE : 0) | STATE_FOCUSED;
+ testStates(aID, expectedStates, 0, unexpectedStates, 0);
+
+ this.node.focus();
+ }
+
+ this.finalCheck = function focusLink_finalCheck()
+ {
+ var expectedStates = (aSelectedAfter ? STATE_FOCUSABLE | STATE_FOCUSED : 0);
+ var unexpectedStates = (!aSelectedAfter ? STATE_FOCUSABLE | STATE_FOCUSED : 0);
+ testStates(aID, expectedStates, 0, unexpectedStates, 0);
+ }
+
+ this.getID = function focusLink_getID()
+ {
+ return "focus hyperlink " + prettyName(aID);
+ }
+}
diff --git a/accessible/tests/mochitest/hyperlink/test_general.html b/accessible/tests/mochitest/hyperlink/test_general.html
new file mode 100644
index 000000000..e6e098ecc
--- /dev/null
+++ b/accessible/tests/mochitest/hyperlink/test_general.html
@@ -0,0 +1,279 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=418368
+-->
+<head>
+ <title>nsIHyperLinkAccessible chrome tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript"
+ src="hyperlink.js"></script>
+
+ <script type="application/javascript">
+ function testThis(aID, aAcc, aRole, aAnchors, aName, aValid, aStartIndex,
+ aEndIndex)
+ {
+ testRole(aAcc, aRole);
+ is(aAcc.anchorCount, aAnchors, "Wrong number of anchors for ID "
+ + aID + "!");
+ is(aAcc.getAnchor(0).name, aName, "Wrong name for ID "
+ + aID + "!");
+ is(aAcc.valid, aValid, "No correct valid state for ID "
+ + aID + "!");
+ is(aAcc.startIndex, aStartIndex, "Wrong startIndex value for ID "
+ + aID + "!");
+ is(aAcc.endIndex, aEndIndex, "Wrong endIndex value for ID "
+ + aID + "!");
+ }
+
+ function testAction(aId, aAcc, aActionName)
+ {
+ var actionCount = aActionName ? 1 : 0;
+ is(aAcc.actionCount, actionCount,
+ "Wrong actions number for ID " + aId);
+ try {
+ is(aAcc.getActionName(0), aActionName,
+ "Wrong action name for ID " + aId);
+ } catch (e) {
+ if (actionCount)
+ ok(false, "Exception on action name getting for ID " + aId);
+ else
+ ok(true, "Correct action name for ID " + aId);
+ }
+ }
+
+ //gA11yEventDumpToConsole = true; // debug stuff
+ function doPreTest()
+ {
+ waitForImageMap("imgmap", doTest);
+ }
+
+ var gQueue = null;
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // normal hyperlink
+ var normalHyperlinkAcc = getAccessible("NormalHyperlink",
+ [nsIAccessibleHyperLink]);
+ testThis("NormalHyperlink", normalHyperlinkAcc, ROLE_LINK, 1,
+ "Mozilla Foundation", true, 17, 18);
+ is(normalHyperlinkAcc.getURI(0).spec, "http://www.mozilla.org/",
+ "URI wrong for normalHyperlinkElement!");
+ testStates(normalHyperlinkAcc, STATE_LINKED, 0);
+
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA hyperlink
+ var ariaHyperlinkAcc = getAccessible("AriaHyperlink",
+ [nsIAccessibleHyperLink]);
+ testThis("AriaHyperlink", ariaHyperlinkAcc, ROLE_LINK, 1,
+ "Mozilla Foundation Home", true, 30, 31);
+ testStates(ariaHyperlinkAcc, STATE_LINKED, 0);
+ testAction("AriaHyperlink", ariaHyperlinkAcc, "click");
+
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA hyperlink with status invalid
+ var invalidAriaHyperlinkAcc = getAccessible("InvalidAriaHyperlink",
+ [nsIAccessibleHyperLink]);
+ is(invalidAriaHyperlinkAcc.valid, false, "Should not be valid!");
+ testStates(invalidAriaHyperlinkAcc, STATE_LINKED, 0);
+
+ //////////////////////////////////////////////////////////////////////////
+ // image map and its link children
+
+ var imageMapHyperlinkAcc = getAccessible("imgmap",
+ [nsIAccessibleHyperLink]);
+ testThis("imgmap", imageMapHyperlinkAcc, ROLE_IMAGE_MAP, 2, "b", true,
+ 79, 80);
+ is(imageMapHyperlinkAcc.getURI(0).spec,
+ "http://www.bbc.co.uk/radio4/atoz/index.shtml#b", "URI wrong!");
+ is(imageMapHyperlinkAcc.getURI(1).spec,
+ "http://www.bbc.co.uk/radio4/atoz/index.shtml#a", "URI wrong!");
+ testStates(imageMapHyperlinkAcc, 0, 0);
+
+ var area1 = getAccessible(imageMapHyperlinkAcc.firstChild,
+ [nsIAccessibleHyperLink]);
+ testThis("Area1", area1, ROLE_LINK, 1, "b", true, 0, 1);
+ is(area1.getURI(0).spec,
+ "http://www.bbc.co.uk/radio4/atoz/index.shtml#b", "URI wrong!");
+ testStates(area1, (STATE_LINKED));
+
+ var area2 = getAccessible(area1.nextSibling,
+ [nsIAccessibleHyperLink]);
+ testThis("Area2", area2, ROLE_LINK, 1, "a", true, 1, 2);
+ is(area2.getURI(0).spec,
+ "http://www.bbc.co.uk/radio4/atoz/index.shtml#a", "URI wrong!");
+ testStates(area2, (STATE_LINKED));
+
+ //////////////////////////////////////////////////////////////////////////
+ // empty hyperlink
+ var EmptyHLAcc = getAccessible("emptyLink",
+ [nsIAccessibleHyperLink]);
+ testThis("emptyLink", EmptyHLAcc, ROLE_LINK, 1, null, true, 93, 94);
+ testStates(EmptyHLAcc, (STATE_FOCUSABLE | STATE_LINKED), 0);
+ testAction("emptyLink", EmptyHLAcc, "jump");
+
+ //////////////////////////////////////////////////////////////////////////
+ // normal hyperlink with embedded span
+ var hyperlinkWithSpanAcc = getAccessible("LinkWithSpan",
+ [nsIAccessibleHyperLink]);
+ testThis("LinkWithSpan", hyperlinkWithSpanAcc, ROLE_LINK, 1,
+ "Heise Online", true, 119, 120);
+ is(hyperlinkWithSpanAcc.getURI(0).spec, "http://www.heise.de/",
+ "URI wrong for hyperlinkElementWithSpan!");
+ testStates(hyperlinkWithSpanAcc, STATE_LINKED, 0);
+ testAction("LinkWithSpan", hyperlinkWithSpanAcc, "jump");
+
+ //////////////////////////////////////////////////////////////////////////
+ // Named anchor, should never have state_linked
+ var namedAnchorAcc = getAccessible("namedAnchor",
+ [nsIAccessibleHyperLink]);
+ testThis("namedAnchor", namedAnchorAcc, ROLE_LINK, 1,
+ "This should never be of state_linked", true, 196, 197);
+ testStates(namedAnchorAcc, STATE_SELECTABLE,
+ 0, (STATE_FOCUSABLE | STATE_LINKED));
+ testAction("namedAnchor", namedAnchorAcc, "");
+
+ //////////////////////////////////////////////////////////////////////////
+ // No link (hasn't any attribute), should never have state_linked
+ var noLinkAcc = getAccessible("noLink",
+ [nsIAccessibleHyperLink]);
+ testThis("noLink", noLinkAcc, ROLE_LINK, 1,
+ "This should never be of state_linked", true, 254, 255);
+ testStates(noLinkAcc, 0, 0, (STATE_FOCUSABLE | STATE_LINKED));
+ testAction("noLink", noLinkAcc, "");
+
+ //////////////////////////////////////////////////////////////////////////
+ // Link with registered 'click' event, should have state_linked
+ var linkWithClickAcc = getAccessible("linkWithClick",
+ [nsIAccessibleHyperLink]);
+ testThis("linkWithClick", linkWithClickAcc, ROLE_LINK, 1,
+ "This should have state_linked", true, 292, 293);
+ testStates(linkWithClickAcc, STATE_LINKED, 0);
+ testAction("linkWithClick", linkWithClickAcc, "click");
+
+ //////////////////////////////////////////////////////////////////////////
+ // Maps to group links (bug 431615).
+ var linksMapAcc = getAccessible("linksmap");
+
+ //////////////////////////////////////////////////////////////////////////
+ // Link with title attribute, no name from the subtree (bug 438325).
+ var id = "linkWithTitleNoNameFromSubtree";
+ var linkAcc = getAccessible(id, [nsIAccessibleHyperLink]);
+ testThis(id, linkAcc, ROLE_LINK, 1, "Link with title", true, 344, 345);
+ testStates(linkAcc, STATE_LINKED, 0);
+ testAction(id, linkAcc, "jump");
+
+ //////////////////////////////////////////////////////////////////////////
+ // Link with title attribute, name from the subtree - onscreen name
+ // (bug 438325).
+ id = "linkWithTitleNameFromSubtree";
+ linkAcc = getAccessible(id, [nsIAccessibleHyperLink]);
+ testThis(id, linkAcc, ROLE_LINK, 1, "the name from subtree", true, 393,
+ 394);
+ testStates(linkAcc, STATE_LINKED, 0);
+ testAction(id, linkAcc, "jump");
+
+ //////////////////////////////////////////////////////////////////////////
+ // Link with title attribute, name from the nested html:img (bug 438325).
+ id = "linkWithTitleNameFromImg";
+ linkAcc = getAccessible(id, [nsIAccessibleHyperLink]);
+ testThis(id, linkAcc, ROLE_LINK, 1, "The title for link", true, 447,
+ 448);
+ testStates(linkAcc, STATE_LINKED, 0);
+ testAction(id, linkAcc, "jump");
+
+ //////////////////////////////////////////////////////////////////////////
+ // Text accessible shouldn't implement nsIAccessibleHyperLink
+ var res = isAccessible(getNode("namedAnchor").firstChild,
+ [nsIAccessibleHyperLink]);
+ ok(!res, "Text accessible shouldn't implement nsIAccessibleHyperLink");
+
+ //////////////////////////////////////////////////////////////////////////
+ // Test focus
+ gQueue = new eventQueue();
+
+ gQueue.push(new focusLink("NormalHyperlink", true));
+ gQueue.push(new focusLink("AriaHyperlink", true));
+ gQueue.push(new focusLink("InvalidAriaHyperlink", false));
+ gQueue.push(new focusLink("LinkWithSpan", true));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doPreTest);
+ </script>
+
+</head>
+<body><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418368">Mozilla Bug 418368</a
+ ><p id="display"></p
+ ><div id="content" style="display: none"></div
+ ><pre id="test">
+ </pre
+ ><br
+ >Simple link:<br
+ ><a id="NormalHyperlink" href="http://www.mozilla.org">Mozilla Foundation</a
+ ><br>ARIA link:<br
+ ><span id="AriaHyperlink" role="link"
+ onclick="window.open('http://www.mozilla.org/');"
+ tabindex="0">Mozilla Foundation Home</span
+ ><br
+ >Invalid, non-focusable hyperlink:<br
+ ><span id="InvalidAriaHyperlink" role="link" aria-invalid="true"
+ onclick="window.open('http:/www.mozilla.org/');">Invalid link</span
+ ><br>Image map:<br
+ ><map name="atoz_map"
+ ><area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b"
+ coords="17,0,30,14"
+ alt="b"
+ shape="rect"></area
+ ><area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#a"
+ coords="0,0,13,14"
+ alt="a"
+ shape="rect"></area
+ ></map
+ ><img width="447" id="imgmap"
+ height="15"
+ usemap="#atoz_map"
+ src="../letters.gif"><br>Empty link:<br
+ ><a id="emptyLink" href=""><img src=""></a
+ ><br>Link with embedded span<br
+ ><a id="LinkWithSpan" href="http://www.heise.de/"><span lang="de">Heise Online</span></a
+ ><br>Named anchor, must not have "linked" state for it to be exposed correctly:<br
+ ><a id="namedAnchor" name="named_anchor">This should never be of state_linked</a
+ ><br>Link having no attributes, must not have "linked" state:<a id="noLink"
+ >This should never be of state_linked</a
+ ><br>Link with registered 'click' event: <a id="linkWithClick" onclick="var clicked = true;"
+ >This should have state_linked</a
+ ><br>Link with title attribute (no name from subtree): <a
+ id="linkWithTitleNoNameFromSubtree" href="http://www.heise.de/"
+ title="Link with title"><img src=""/></a
+ ><br>Link with title attribute (name from subtree): <a
+ id="linkWithTitleNameFromSubtree" href="http://www.heise.de/"
+ title="Link with title">the name from subtree</a
+ ><br>Link with title attribute (name from nested image): <a
+ id="linkWithTitleNameFromImg" href="http://www.heise.de/"
+ title="Link with title"><img src="" alt="The title for link"/></a
+ ><br><br>Map that is used to group links (www.w3.org/TR/WCAG10-HTML-TECHS/#group-bypass), also see the bug 431615:<br
+ ><map id="linksmap" title="Site navigation"><ul
+ ><li><a href="http://mozilla.org">About the project</a></li
+ ><li><a href="http://mozilla.org">Sites and sounds</a></li
+ ></ul
+ ></map
+></body>
+</html>
diff --git a/accessible/tests/mochitest/hyperlink/test_general.xul b/accessible/tests/mochitest/hyperlink/test_general.xul
new file mode 100644
index 000000000..c7b838a15
--- /dev/null
+++ b/accessible/tests/mochitest/hyperlink/test_general.xul
@@ -0,0 +1,97 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="test for nsIAccessibleHyperLink interface on XUL:label elements">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript"
+ src="hyperlink.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function testThis(aID, aAcc, aRole, aAnchorCount, aAnchorName, aURI,
+ aStartIndex, aEndIndex, aValid)
+ {
+ testRole(aID, aRole);
+ is(aAcc.anchorCount, aAnchorCount, "Wrong number of anchors for ID "
+ + aID + "!");
+ is(aAcc.getAnchor(0).name, aAnchorName, "Wrong name for ID " + aID + "!");
+ is(aAcc.getURI(0).spec, aURI, "URI wrong for ID " + aID + "!");
+ is(aAcc.startIndex, aStartIndex, "Wrong startIndex value for ID " + aID
+ + "!");
+ is(aAcc.endIndex, aEndIndex, "Wrong endIndex value for ID " + aID + "!");
+ is(aAcc.valid, aValid, "Wrong valid state for ID " + aID + "!");
+ }
+
+ var gQueue = null;
+ function doTest()
+ {
+ var linkedLabelAcc = getAccessible("linkedLabel",
+ [nsIAccessibleHyperLink]);
+ testThis("linkedLabel", linkedLabelAcc, ROLE_LINK, 1,
+ "Mozilla Foundation home", "http://www.mozilla.org/", 1, 2,
+ true);
+ testStates(linkedLabelAcc, STATE_LINKED, 0);
+
+ var labelWithValueAcc = getAccessible("linkLabelWithValue",
+ [nsIAccessibleHyperLink]);
+ testThis("linkLabelWithValue", labelWithValueAcc, ROLE_LINK, 1,
+ "Mozilla Foundation", "http://www.mozilla.org/", 2, 3, true,
+ false, true);
+ testStates(labelWithValueAcc, STATE_LINKED, EXT_STATE_HORIZONTAL);
+
+ var normalLabelAcc = getAccessible("normalLabel");
+ testRole(normalLabelAcc, ROLE_LABEL);
+ is(normalLabelAcc.name, "This label should not be a link",
+ "Wrong name for normal label!");
+ testStates(normalLabelAcc, 0, 0, (STATE_FOCUSABLE | STATE_LINKED));
+
+ //////////////////////////////////////////////////////////////////////////
+ // Test focus
+
+ gQueue = new eventQueue();
+
+ gQueue.push(new focusLink("linkedLabel", true));
+ gQueue.push(new focusLink("linkLabelWithValue", true));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=421066"
+ title="Implement Mochitests for the nsIAccessibleHyperLink interface on XUL:label elements">
+ Mozilla Bug 421066
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <label id="linkedLabel" class="text-link" href="http://www.mozilla.org/">
+ Mozilla Foundation home</label>
+ <label id="linkLabelWithValue" value="Mozilla Foundation" class="text-link"
+ href="http://www.mozilla.org/" />
+ <label id="normalLabel" value="This label should not be a link" />
+</window>
diff --git a/accessible/tests/mochitest/hypertext/a11y.ini b/accessible/tests/mochitest/hypertext/a11y.ini
new file mode 100644
index 000000000..27f878f74
--- /dev/null
+++ b/accessible/tests/mochitest/hypertext/a11y.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/letters.gif
+
+[test_general.html]
+[test_update.html]
diff --git a/accessible/tests/mochitest/hypertext/test_general.html b/accessible/tests/mochitest/hypertext/test_general.html
new file mode 100644
index 000000000..68018821f
--- /dev/null
+++ b/accessible/tests/mochitest/hypertext/test_general.html
@@ -0,0 +1,156 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=428248
+-->
+<head>
+ <title>nsIHyper>TextAccessible chrome tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ var gParagraphAcc;
+
+ function testLinkIndexAtOffset(aID, aOffset, aIndex)
+ {
+ var htAcc = getAccessible(aID, [nsIAccessibleHyperText]);
+ is(htAcc.getLinkIndexAtOffset(aOffset), aIndex,
+ "Wrong link index at offset " + aOffset + " for ID " + aID + "!");
+ }
+
+ function testThis(aID, aCharIndex, aExpectedLinkIndex, aName)
+ {
+ testLinkIndexAtOffset(gParagraphAcc, aCharIndex, aExpectedLinkIndex);
+
+ var linkAcc = gParagraphAcc.getLinkAt(aExpectedLinkIndex);
+ ok(linkAcc, "No accessible for link " + aID + "!");
+
+ var linkIndex = gParagraphAcc.getLinkIndex(linkAcc);
+ is(linkIndex, aExpectedLinkIndex, "Wrong link index for " + aID + "!");
+
+ // Just test the link's name to make sure we get the right one.
+ is(linkAcc.getAnchor(0).name, aName, "Wrong name for " + aID + "!");
+ }
+
+ //gA11yEventDumpToConsole = true;
+ function doPreTest()
+ {
+ waitForImageMap("imgmap", doTest);
+ }
+
+ function doTest()
+ {
+ // Test link count
+ gParagraphAcc = getAccessible("testParagraph", [nsIAccessibleHyperText]);
+ is(gParagraphAcc.linkCount, 7, "Wrong link count for paragraph!");
+
+ // normal hyperlink
+ testThis("NormalHyperlink", 14, 0, "Mozilla Foundation");
+
+ // ARIA hyperlink
+ testThis("AriaHyperlink", 27, 1, "Mozilla Foundation Home");
+
+ // ARIA hyperlink with status invalid
+ testThis("InvalidAriaHyperlink", 63, 2, "Invalid link");
+
+ // image map, but not its link children. They are not part of hypertext.
+ testThis("imgmap", 76, 3, "b");
+
+ // empty hyperlink
+ testThis("emptyLink", 90, 4, null);
+
+ // normal hyperlink with embedded span
+ testThis("LinkWithSpan", 116, 5, "Heise Online");
+
+ // Named anchor
+ testThis("namedAnchor", 193, 6, "This should never be of state_linked");
+
+ // Paragraph with link
+ var p2 = getAccessible("p2", [nsIAccessibleHyperText]);
+ var link = p2.getLinkAt(0);
+ is(link, p2.getChildAt(0), "Wrong link for p2");
+ is(p2.linkCount, 1, "Wrong link count for p2");
+
+ // getLinkIndexAtOffset, causes the offsets to be cached;
+ testLinkIndexAtOffset("p4", 0, 0); // 1st 'mozilla' link
+ testLinkIndexAtOffset("p4", 1, 1); // 2nd 'mozilla' link
+ testLinkIndexAtOffset("p4", 2, -1); // ' ' of ' te' text node
+ testLinkIndexAtOffset("p4", 3, -1); // 't' of ' te' text node
+ testLinkIndexAtOffset("p4", 5, -1); // 'x' of 'xt ' text node
+ testLinkIndexAtOffset("p4", 7, -1); // ' ' of 'xt ' text node
+ testLinkIndexAtOffset("p4", 8, 2); // 3d 'mozilla' link
+ testLinkIndexAtOffset("p4", 9, 2); // the end, latest link
+
+ // the second pass to make sure link indexes are calculated propertly from
+ // cached offsets.
+ testLinkIndexAtOffset("p4", 0, 0); // 1st 'mozilla' link
+ testLinkIndexAtOffset("p4", 1, 1); // 2nd 'mozilla' link
+ testLinkIndexAtOffset("p4", 2, -1); // ' ' of ' te' text node
+ testLinkIndexAtOffset("p4", 3, -1); // 't' of ' te' text node
+ testLinkIndexAtOffset("p4", 5, -1); // 'x' of 'xt ' text node
+ testLinkIndexAtOffset("p4", 7, -1); // ' ' of 'xt ' text node
+ testLinkIndexAtOffset("p4", 8, 2); // 3d 'mozilla' link
+ testLinkIndexAtOffset("p4", 9, 2); // the end, latest link
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doPreTest);
+ </script>
+
+</head>
+<body>
+
+ <a target="_blank"
+ title="Create tests for NSIAccessibleHyperlink interface"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=418368">
+ Mozilla Bug 418368
+ </a><br>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <p id="testParagraph"><br
+ >Simple link:<br
+ ><a id="NormalHyperlink" href="http://www.mozilla.org">Mozilla Foundation</a><br
+ >ARIA link:<br
+ ><span id="AriaHyperlink" role="link"
+ onclick="window.open('http://www.mozilla.org/');"
+ tabindex="0">Mozilla Foundation Home</span><br
+ >Invalid, non-focusable hyperlink:<br
+ ><span id="InvalidAriaHyperlink" role="link" aria-invalid="true"
+ onclick="window.open('http:/www.mozilla.org/');">Invalid link</span><br
+ >Image map:<br
+ ><map name="atoz_map"><area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b"
+ coords="17,0,30,14"
+ alt="b"
+ shape="rect"></area
+ ><area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#a"
+ coords="0,0,13,14"
+ alt="a"
+ shape="rect"></area></map
+ ><img width="447" id="imgmap"
+ height="15"
+ usemap="#atoz_map"
+ src="../letters.gif"></img><br
+ >Empty link:<br
+ ><a id="emptyLink" href=""><img src=""></img></a><br
+ >Link with embedded span<br
+ ><a id="LinkWithSpan" href="http://www.heise.de/"><span lang="de">Heise Online</span></a><br
+ >Named anchor, must not have "linked" state for it to be exposed correctly:<br
+ ><a id="namedAnchor" name="named_anchor">This should never be of state_linked</a>
+ </p>
+ <p id="p2"><a href="http://mozilla.org">mozilla.org</a></p>
+ <p id="p4"><a href="www">mozilla</a><a href="www">mozilla</a><span> te</span><span>xt </span><a href="www">mozilla</a></p>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/hypertext/test_update.html b/accessible/tests/mochitest/hypertext/test_update.html
new file mode 100644
index 000000000..23ac8b2c8
--- /dev/null
+++ b/accessible/tests/mochitest/hypertext/test_update.html
@@ -0,0 +1,236 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>nsIHyper>TextAccessible in dynamic tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ const kLinksCount = 128;
+ function addLinks(aContainerID)
+ {
+ this.containerNode = getNode(aContainerID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.containerNode)
+ ];
+
+ this.invoke = function addLinks_invoke()
+ {
+ for (var jdx = 0; jdx < kLinksCount; jdx++) {
+ var a = document.createElement("a");
+ a.setAttribute("href", "mozilla.org");
+ a.textContent = "mozilla";
+ this.containerNode.appendChild(a);
+
+ var span = document.createElement("span");
+ span.textContent = " text ";
+ this.containerNode.appendChild(span);
+ }
+ }
+
+ this.finalCheck = function addLinks_finalCheck()
+ {
+ // getLinkAt and getLinkIndex.
+ var htAcc = getAccessible(this.containerNode, [nsIAccessibleHyperText]);
+ for (var jdx = 0; jdx < kLinksCount; jdx++) {
+ var link = htAcc.getLinkAt(jdx);
+ ok(link, "No link at index " + jdx + " for '" + aContainerID + "'");
+
+ var linkIdx = htAcc.getLinkIndex(link);
+ is(linkIdx, jdx, "Wrong link index for '" + aContainerID + "'!");
+ }
+ }
+
+ this.getID = function addLinks_getID()
+ {
+ return "Add links for '" + aContainerID + "'";
+ }
+ }
+
+ function updateText(aContainerID)
+ {
+ this.containerNode = getNode(aContainerID);
+ this.container = getAccessible(this.containerNode, nsIAccessibleHyperText);
+ this.text = this.container.firstChild;
+ this.textNode = this.text.DOMNode;
+ this.textLen = this.textNode.data.length;
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_TEXT_INSERTED, this.containerNode)
+ ];
+
+ this.invoke = function updateText_invoke()
+ {
+ is(this.container.getLinkIndexAtOffset(this.textLen), 0,
+ "Wrong intial text offsets!");
+
+ this.text.DOMNode.appendData(" my");
+ }
+
+ this.finalCheck = function updateText_finalCheck()
+ {
+ is(this.container.getLinkIndexAtOffset(this.textLen), -1,
+ "Text offsets weren't updated!");
+ }
+
+ this.getID = function updateText_getID()
+ {
+ return "update text for '" + aContainerID + "'";
+ }
+ }
+
+ /**
+ * Text offsets must be updated when hypertext child is removed.
+ */
+ function removeChild(aContainerID, aChildID, aInitialText, aFinalText)
+ {
+ this.containerNode = getNode(aContainerID);
+ this.container = getAccessible(this.containerNode, nsIAccessibleText);
+ this.childNode = getNode(aChildID);
+
+ // Call first to getText so offsets are cached
+ is(this.container.getText(0, -1), aInitialText,
+ "Wrong text before child removal");
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.containerNode)
+ ];
+
+ this.invoke = function removeChild_invoke()
+ {
+ this.containerNode.removeChild(this.childNode);
+ }
+
+ this.finalCheck = function removeChild_finalCheck()
+ {
+ is(this.container.getText(0, -1), aFinalText,
+ "Wrong text after child removal");
+ is(this.container.characterCount, aFinalText.length,
+ "Wrong text after child removal");
+ }
+
+ this.getID = function removeChild_getID()
+ {
+ return "check text after removing child from '" + aContainerID + "'";
+ }
+ }
+
+ function removeFirstChild(aContainer)
+ {
+ this.ht = getAccessible(aContainer, [ nsIAccessibleHyperText ]);
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, aContainer)
+ ];
+
+ this.invoke = function removeFirstChild_invoke()
+ {
+ is(this.ht.linkCount, 2, "Wrong embedded objects count before removal");
+
+ getNode(aContainer).removeChild(getNode(aContainer).firstElementChild);
+ }
+
+ this.finalCheck = function removeFirstChild_finalCheck()
+ {
+ // check list index before link count
+ is(this.ht.getLinkIndex(this.ht.firstChild), 0, "Wrong child index");
+ is(this.ht.linkCount, 1, "Wrong embedded objects count after removal");
+ }
+
+ this.getID = function removeFirstChild_getID()
+ {
+ return "Remove first child and check embedded object indeces";
+ }
+ }
+
+ function removeLastChild(aContainer)
+ {
+ this.ht = getAccessible(aContainer, [ nsIAccessibleHyperText ]);
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, aContainer)
+ ];
+
+ this.invoke = function removeLastChild_invoke()
+ {
+ is(this.ht.linkCount, 1, "Wrong embedded objects count before removal");
+
+ getNode(aContainer).removeChild(getNode(aContainer).lastElementChild);
+ }
+
+ this.finalCheck = function removeLastChild_finalCheck()
+ {
+ is(this.ht.linkCount, 0, "Wrong embedded objects count after removal");
+
+ var link = null;
+ try {
+ link = this.ht.getLinkAt(0);
+ } catch (e) { }
+ ok(!link, "No embedded object is expected");
+ }
+
+ this.getID = function removeLastChild_getID()
+ {
+ return "Remove last child and try its embedded object";
+ }
+ }
+
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTest()
+ {
+ gQueue = new eventQueue();
+ gQueue.push(new addLinks("p1"));
+ gQueue.push(new updateText("p2"));
+ gQueue.push(new removeChild("div1","div2",
+ "hello my good friend", "hello friend"));
+ gQueue.push(new removeFirstChild("c4"));
+ gQueue.push(new removeLastChild("c5"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Cache links within hypertext accessible"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=572394">
+ Mozilla Bug 572394
+ </a>
+ <a target="_blank"
+ title="Text offsets don't get updated when text of first child text accessible is changed"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=625009">
+ Mozilla Bug 625009
+ </a>
+ <a target="_blank"
+ title="Crash in nsHyperTextAccessible::GetText()"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=630841">
+ Mozilla Bug 630841
+ </a><br>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <p id="p1"></p>
+ <p id="p2"><b>hello</b><a>friend</a></p>
+ <div id="div1">hello<span id="div2"> my<span id="div3"> good</span></span> friend</span></div>
+ <form id="c4">
+ <label for="c4_input">label</label>
+ <input id="c4_input">
+ </form>
+ <div id="c5">TextLeaf<input id="c5_input"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/jsat/a11y.ini b/accessible/tests/mochitest/jsat/a11y.ini
new file mode 100644
index 000000000..5acc3df90
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/a11y.ini
@@ -0,0 +1,30 @@
+[DEFAULT]
+support-files =
+ dom_helper.js
+ gestures.json
+ jsatcommon.js
+ output.js
+ doc_traversal.html
+ doc_content_integration.html
+ doc_content_text.html
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/moz.png
+skip-if = (os == 'win' && (os_version == '5.1' || os_version == '5.2'))
+
+[test_alive.html]
+[test_content_integration.html]
+skip-if = buildapp == 'mulet'
+[test_content_text.html]
+skip-if = buildapp == 'mulet'
+[test_explicit_names.html]
+[test_gesture_tracker.html]
+[test_hints.html]
+[test_landmarks.html]
+[test_live_regions.html]
+[test_output_mathml.html]
+[test_output.html]
+[test_quicknav_modes.html]
+[test_tables.html]
+[test_pointer_relay.html]
+[test_traversal.html]
+[test_traversal_helper.html]
diff --git a/accessible/tests/mochitest/jsat/doc_content_integration.html b/accessible/tests/mochitest/jsat/doc_content_integration.html
new file mode 100644
index 000000000..d62c000cb
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/doc_content_integration.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Traversal Rule test document</title>
+ <meta charset="utf-8" />
+ <script>
+ var frameContents = '<html>' +
+ '<head><title>such app</title></head>' +
+ '<body>' +
+ '<h1>wow</h1>' +
+ '<ul>' +
+ '<li><label><input type="checkbox">many option</label></li>' +
+ '</ul>' +
+ '<label for="r">much range</label>' +
+ '<input min="0" max="10" value="5" type="range" id="r">' +
+ '</body>' +
+ '</html>';
+
+ function showAlert() {
+ document.getElementById('alert').hidden = false;
+ }
+
+ function hideAlert() {
+ document.getElementById('alert').hidden = true;
+ }
+
+ function ariaShowBack() {
+ document.getElementById('back').setAttribute('aria-hidden', false);
+ }
+
+ function ariaHideBack() {
+ document.getElementById('back').setAttribute('aria-hidden', true);
+ }
+
+ function ariaShowIframe() {
+ document.getElementById('iframe').setAttribute('aria-hidden', false);
+ }
+
+ function ariaHideIframe() {
+ document.getElementById('iframe').setAttribute('aria-hidden', true);
+ }
+
+ function renameFruit() {
+ document.getElementById('fruit').setAttribute('aria-label', 'banana');
+ }
+
+ function renameSlider() {
+ document.getElementById('slider').setAttribute(
+ 'aria-label', 'mover');
+ }
+
+ function changeSliderValue() {
+ document.getElementById('slider').setAttribute('aria-valuenow', '5');
+ document.getElementById('slider').setAttribute(
+ 'aria-valuetext', 'medium');
+ }
+
+ function toggleLight() {
+ var lightSwitch = document.getElementById('light');
+ lightSwitch.setAttribute('aria-checked',
+ lightSwitch.getAttribute('aria-checked') === 'true' ? 'false' : 'true');
+ }
+
+ </script>
+ <style>
+ #windows {
+ position: relative;
+ width: 320px;
+ height: 480px;
+ }
+
+ #windows > iframe {
+ z-index: 1;
+ }
+
+ #windows > div[role='dialog'] {
+ z-index: 2;
+ background-color: pink;
+ }
+
+ #windows > * {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ }
+
+ iframe {
+ width: 100%;
+ height: 100%;
+ }
+
+ </style>
+
+</head>
+<body>
+ <div>Phone status bar</div>
+ <div id="windows">
+ <button id="back">Back</button>
+ <div role="dialog" id="alert" hidden>
+ <h1>This is an alert!</h1>
+ <p>Do you agree?</p>
+ <button onclick="setTimeout(hideAlert, 500)">Yes</button>
+ <button onclick="hideAlert()">No</button>
+ </div>
+ <div id="appframe"></div>
+ </div>
+ <button id="home">Home</button>
+ <button id="fruit" aria-label="apple"></button>
+ <span id="light" role="switch" aria-label="Light" aria-checked="false" onclick="toggleLight()"></span>
+ <div id="live" aria-live="polite" aria-label="live">
+ <div id="slider" role="slider" aria-label="slider" aria-valuemin="0"
+ aria-valuemax="10" aria-valuenow="0"></div>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/jsat/doc_content_text.html b/accessible/tests/mochitest/jsat/doc_content_text.html
new file mode 100644
index 000000000..4e73dc6e7
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/doc_content_text.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Text content test document</title>
+ <meta charset="utf-8" />
+ </head>
+ <body>
+ <p>These are my awards, Mother. From Army.
+ The seal is for marksmanship, and the gorilla is for sand racing.</p>
+ <p>You're a good guy, mon frere. That means brother in French.
+ I don't know how I know that. I took four years of Spanish.</p>
+ <textarea>Please refrain from Mayoneggs during this salmonella scare.</textarea>
+ <label>So we don't get dessert?</label><input type="text">
+ </body>
+</html> \ No newline at end of file
diff --git a/accessible/tests/mochitest/jsat/doc_traversal.html b/accessible/tests/mochitest/jsat/doc_traversal.html
new file mode 100644
index 000000000..4c104b6b7
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/doc_traversal.html
@@ -0,0 +1,164 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Traversal Rule test document</title>
+ <meta charset="utf-8" />
+ <style>
+ .content:before {
+ content: "Content";
+ }
+ </style>
+</head>
+<body>
+ <header id="header-1">
+ <h3 id="heading-1">A small first heading</h3>
+ <form>
+ <label for="input-1-1">Name:</label>
+ <input id="input-1-1">
+ <label id="label-1-2">Favourite Ice Cream Flavour:<input id="input-1-2"></label>
+ <button id="button-1-1">Magic Button</button>
+ <label for="radio-1-1">Radios are old: </label>
+ <input id="radio-1-1" type="radio">
+ <label for="radio-1-2">Radios are new: </label>
+ <input id="radio-1-2" type="radio">
+ <label for="input-1-3">Password:</label>
+ <input id="input-1-3" type="password">
+ <label for="input-1-4">Unlucky number:</label>
+ <input id="input-1-4" type="tel">
+ <input id="button-1-2" type="button" value="Fun">
+ <label for="checkbox-1-1">Check me: </label>
+ <input id="checkbox-1-1" type="checkbox">
+ <select id="select-1-1">
+ <option>Value 1</option>
+ <option>Value 2</option>
+ <option>Value 3</option>
+ </select>
+ <select id="select-1-2" multiple="true">
+ <option>Value 1</option>
+ <option>Value 2</option>
+ <option>Value 3</option>
+ </select>
+ <label for="checkbox-1-2">Check me too: </label>
+ <input id="checkbox-1-2" type="checkbox">
+ <label for="checkbox-1-3">But not me: </label>
+ <input id="checkbox-1-3" type="checkbox" aria-hidden="true">
+ <label for="checkbox-1-4">Or me! </label>
+ <input id="checkbox-1-4" type="checkbox" style="visibility:hidden">
+ <select id="select-1-3" size="3">
+ <option>Value 1</option>
+ <option>Value 2</option>
+ <option>Value 3</option>
+ </select>
+ <label for="input-1-5">Electronic mailing address:</label>
+ <input id="input-1-5" type="email">
+ <input id="button-1-3" type="submit" value="Submit">
+ </form>
+ </header>
+ <main id="main-1">
+ <h2 id="heading-2">A larger second</h2>
+ <div id="heading-3" role="heading">ARIA is fun</div>
+ <input id="button-2-1" type="button" value="More Fun">
+ <div id="button-2-2" tabindex="0" role="button">ARIA fun</div>
+ <div id="button-2-3" tabindex="0" role="button" aria-pressed="false">My little togglebutton</div>
+ <div id="button-2-4" tabindex="0" role="spinbutton">ARIA fun</div>
+ </main>
+ <h1 id="heading-4" style="display:none">Invisible header</h1>
+ <dl id="list-1">
+ <dt id="listitem-1-1">Programming Language</dt>
+ <dd>A esoteric weapon wielded by only the most formidable warriors,
+ for its unrelenting strict power is unfathomable.</dd>
+ </dl>
+ <ul id="list-2" onclick="alert('hi');">
+ <li id="listitem-2-1">Lists of Programming Languages</li>
+ <li id="listitem-2-2">Lisp
+ <ol id="list-3">
+ <li id="listitem-3-1">Scheme</li>
+ <li id="listitem-3-2">Racket</li>
+ <li id="listitem-3-3">Clojure</li>
+ <li id="listitem-3-4"><strong>Standard</strong> Lisp</li>
+ <li id="listitem-3-5"><a id="link-0" href="#">Common</a> Lisp</li>
+ <li id="listitem-3-6"><input id="checkbox-1-5" type="checkbox"> LeLisp</li>
+ </ol>
+ </li>
+ <li id="listitem-2-3">JavaScript</li>
+ </ul>
+ <section>
+ <h6 id="heading-5">The last (visible) one!</h6>
+ <img id="image-1" src="http://example.com" alt="">
+ <img id="image-2" src="../moz.png" alt="stuff">
+ <div id="image-3" tabindex="0" role="img">Not actually an image</div>
+ </section>
+ <section>
+ <h4 id="heading-6" aria-hidden="true">Hidden header</h4>
+ <a id="link-1" href="http://www.mozilla.org">Link</a>
+ <a id="anchor-1">Words</a>
+ <a id="link-2" href="http://www.mozilla.org">Link the second</a>
+ <a id="anchor-2">Sentences</a>
+ <a id="link-3" href="http://www.example.com">Link the third</a>
+ </section>
+ <hr id="separator-1">
+ <h6 id="heading-6"></h6>
+ <table id="table-1">
+ <tr>
+ <td>3</td>
+ <td>1</td>
+ </tr>
+ <tr>
+ <td>4</td>
+ <td>1</td>
+ </tr>
+ </table>
+ <section id="grid" role="grid">
+ <ol role="row">
+ <li role="presentation"></li>
+ <li role="columnheader" aria-label="Sunday">S</li>
+ <li role="columnheader">M</li>
+ </ol>
+ <ol role="row">
+ <li role="rowheader" aria-label="Week 1">1</li>
+ <li role="gridcell"><span>3</span><div></div></li>
+ <li role="gridcell"><span>4</span><div>7</div></li>
+ </ol>
+ <ol role="row">
+ <li role="rowheader">2</li>
+ <li role="gridcell"><span>5</span><div role="presentation">8</div></li>
+ <li id="gridcell4" role="gridcell">
+ <span>6</span><div aria-hidden="true"><div class="content"></div></div>
+ </li>
+ </ol>
+ </section>
+ <div id="separator-2" role="separator">Just an innocuous separator</div>
+ <table id="table-2">
+ <thead>
+ <tr>
+ <th>Dirty Words</th>
+ <th>Meaning</th>
+ </tr>
+ </thead>
+ <tfoot>
+ <tr>
+ <td>Mud</td>
+ <td>Wet Dirt</td>
+ </tr>
+ </tfoot>
+ <tbody>
+ <tr>
+ <td>Dirt</td>
+ <td>Messy Stuff</td>
+ </tr>
+ </tbody>
+ </table>
+ <footer id="footer-1">
+ <div id="statusbar-1" role="status">Last sync:<span>2 days ago</span></div>
+ <div aria-label="Last sync: 30min ago" id="statusbar-2" role="status"></div>
+ </footer>
+
+ <span id="switch-1" role="switch" aria-checked="false" aria-label="Light switch"></span>
+ <p>This is a MathML formula <math id="math-1" display="block">
+ <mfrac>
+ <mrow><mi>x</mi><mo>+</mo><mn>1</mn></mrow>
+ <msqrt><mn>3</mn><mo>/</mo><mn>4</mn></msqrt>
+ </mfrac>
+ </math> with some text after.</p>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/jsat/dom_helper.js b/accessible/tests/mochitest/jsat/dom_helper.js
new file mode 100644
index 000000000..c95d19dc1
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/dom_helper.js
@@ -0,0 +1,209 @@
+'use strict';
+
+/* global getMainChromeWindow, AccessFuTest, GestureSettings, GestureTracker,
+ SimpleTest, getBoundsForDOMElm, Point, Utils */
+/* exported loadJSON, eventMap */
+
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+Cu.import('resource://gre/modules/Geometry.jsm');
+
+var win = getMainChromeWindow(window);
+
+/**
+ * Convert inch based point coordinates into pixels.
+ * @param {Array} aPoints Array of coordinates in inches.
+ * @return {Array} Array of coordinates in pixels.
+ */
+function convertPointCoordinates(aPoints) {
+ var dpi = Utils.dpi;
+ return aPoints.map(function convert(aPoint) {
+ return {
+ x: aPoint.x * dpi,
+ y: aPoint.y * dpi,
+ identifier: aPoint.identifier
+ };
+ });
+}
+
+/**
+ * For a given list of points calculate their coordinates in relation to the
+ * document body.
+ * @param {Array} aTouchPoints An array of objects of the following format: {
+ * base: {String}, // Id of an element to server as a base for the touch.
+ * x: {Number}, // An optional x offset from the base element's geometric
+ * // centre.
+ * y: {Number} // An optional y offset from the base element's geometric
+ * // centre.
+ * }
+ * @return {JSON} An array of {x, y} coordinations.
+ */
+function calculateTouchListCoordinates(aTouchPoints) {
+ var coords = [];
+ for (var i = 0, target = aTouchPoints[i]; i < aTouchPoints.length; ++i) {
+ var bounds = getBoundsForDOMElm(target.base);
+ var parentBounds = getBoundsForDOMElm('root');
+ var point = new Point(target.x || 0, target.y || 0);
+ point.scale(Utils.dpi);
+ point.add(bounds[0], bounds[1]);
+ point.add(bounds[2] / 2, bounds[3] / 2);
+ point.subtract(parentBounds[0], parentBounds[0]);
+ coords.push({
+ x: point.x,
+ y: point.y
+ });
+ }
+ return coords;
+}
+
+/**
+ * Send a touch event with specified touchPoints.
+ * @param {Array} aTouchPoints An array of points to be associated with
+ * touches.
+ * @param {String} aName A name of the touch event.
+ */
+function sendTouchEvent(aTouchPoints, aName) {
+ var touchList = sendTouchEvent.touchList;
+ if (aName === 'touchend') {
+ sendTouchEvent.touchList = null;
+ } else {
+ var coords = calculateTouchListCoordinates(aTouchPoints);
+ var touches = [];
+ for (var i = 0; i < coords.length; ++i) {
+ var {x, y} = coords[i];
+ var node = document.elementFromPoint(x, y);
+ var touch = document.createTouch(window, node, aName === 'touchstart' ?
+ 1 : touchList.item(i).identifier, x, y, x, y);
+ touches.push(touch);
+ }
+ touchList = document.createTouchList(touches);
+ sendTouchEvent.touchList = touchList;
+ }
+ var evt = document.createEvent('TouchEvent');
+ evt.initTouchEvent(aName, true, true, window, 0, false, false, false, false,
+ touchList, touchList, touchList);
+ document.dispatchEvent(evt);
+}
+
+sendTouchEvent.touchList = null;
+
+/**
+ * A map of event names to the functions that actually send them.
+ * @type {Object}
+ */
+var eventMap = {
+ touchstart: sendTouchEvent,
+ touchend: sendTouchEvent,
+ touchmove: sendTouchEvent
+};
+
+/**
+ * Attach a listener for the mozAccessFuGesture event that tests its
+ * type.
+ * @param {Array} aExpectedGestures A stack of expected event types.
+ * @param {String} aTitle Title of this sequence, if any.
+ * Note: the listener is removed once the stack reaches 0.
+ */
+function testMozAccessFuGesture(aExpectedGestures, aTitle) {
+ var types = aExpectedGestures;
+ function handleGesture(aEvent) {
+ if (aEvent.detail.type !== types[0].type) {
+ info('Got ' + aEvent.detail.type + ' waiting for ' + types[0].type);
+ // The is not the event of interest.
+ return;
+ }
+ is(!!aEvent.detail.edge, !!types[0].edge);
+ is(aEvent.detail.touches.length, types[0].fingers || 1,
+ 'failed to count fingers: ' + types[0].type);
+ ok(true, 'Received correct mozAccessFuGesture: ' +
+ JSON.stringify(types.shift()) + '. (' + aTitle + ')');
+ if (types.length === 0) {
+ win.removeEventListener('mozAccessFuGesture', handleGesture);
+ if (AccessFuTest.sequenceCleanup) {
+ AccessFuTest.sequenceCleanup();
+ }
+ AccessFuTest.nextTest();
+ }
+ }
+ win.addEventListener('mozAccessFuGesture', handleGesture);
+}
+
+/**
+ * Reset the thresholds and max delays that affect gesture rejection.
+ * @param {Number} aTimeStamp Gesture time stamp.
+ * @param {Boolean} aRemoveDwellThreshold An optional flag to reset dwell
+ * threshold.
+ * @param {Boolean} aRemoveSwipeMaxDuration An optional flag to reset swipe max
+ * duration.
+ */
+function setTimers(aTimeStamp, aRemoveDwellThreshold, aRemoveSwipeMaxDuration) {
+ if (!aRemoveDwellThreshold && !aRemoveSwipeMaxDuration) {
+ return;
+ }
+ if (aRemoveDwellThreshold) {
+ GestureSettings.dwellThreshold = 0;
+ }
+ if (aRemoveSwipeMaxDuration) {
+ GestureSettings.swipeMaxDuration = 0;
+ }
+ GestureTracker.current.clearTimer();
+ GestureTracker.current.startTimer(aTimeStamp);
+}
+
+function resetTimers(aRemoveGestureResolveDelay) {
+ GestureSettings.dwellThreshold = AccessFuTest.dwellThreshold;
+ GestureSettings.swipeMaxDuration = AccessFuTest.swipeMaxDuration;
+ GestureSettings.maxGestureResolveTimeout = aRemoveGestureResolveDelay ?
+ 0 : AccessFuTest.maxGestureResolveTimeout;
+}
+
+/**
+ * An extention to AccessFuTest that adds an ability to test a sequence of
+ * pointer events and their expected mozAccessFuGesture events.
+ * @param {Object} aSequence An object that has a list of pointer events to be
+ * generated and the expected mozAccessFuGesture events.
+ */
+AccessFuTest.addSequence = function AccessFuTest_addSequence(aSequence) {
+ AccessFuTest.addFunc(function testSequence() {
+ testMozAccessFuGesture(aSequence.expectedGestures, aSequence.title);
+ var events = aSequence.events;
+ function fireEvent(aEvent) {
+ var event = {
+ points: convertPointCoordinates(aEvent.points),
+ type: aEvent.type
+ };
+ var timeStamp = Date.now();
+ resetTimers(aEvent.removeGestureResolveDelay);
+ GestureTracker.handle(event, timeStamp);
+ setTimers(timeStamp, aEvent.removeDwellThreshold,
+ aEvent.removeSwipeMaxDuration);
+ processEvents();
+ }
+ function processEvents() {
+ if (events.length === 0) {
+ return;
+ }
+ var event = events.shift();
+ SimpleTest.executeSoon(function() {
+ fireEvent(event);
+ });
+ }
+ processEvents();
+ });
+};
+
+/**
+ * A helper function that loads JSON files.
+ * @param {String} aPath A path to a JSON file.
+ * @param {Function} aCallback A callback to be called on success.
+ */
+function loadJSON(aPath, aCallback) {
+ var request = new XMLHttpRequest();
+ request.open('GET', aPath, true);
+ request.responseType = 'json';
+ request.onload = function onload() {
+ aCallback(request.response);
+ };
+ request.send();
+}
diff --git a/accessible/tests/mochitest/jsat/gestures.json b/accessible/tests/mochitest/jsat/gestures.json
new file mode 100644
index 000000000..111994342
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/gestures.json
@@ -0,0 +1,352 @@
+[
+ {
+ "events": [
+ {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}],
+ "removeGestureResolveDelay": true }
+ ],
+ "expectedGestures": [{ "type": "tap" }]
+ },
+ {
+ "events": [
+ {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointermove",
+ "points": [{"x": 1.03, "y": 1.03, "identifier": 1}]},
+ {"type": "pointerup", "points": [{"x": 1.03, "y": 1.03, "identifier": 1}],
+ "removeGestureResolveDelay": true }
+ ],
+ "expectedGestures": [{ "type": "tap" }]
+ },
+ {
+ "events": [
+ {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}],
+ "removeDwellThreshold": true},
+ {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}
+ ],
+ "expectedGestures": [{ "type": "dwell" }, { "type": "dwellend" }]
+ },
+ {
+ "events": [
+ {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointermove",
+ "points": [{"x": 1.03, "y": 1.02, "identifier": 1}]},
+ {"type": "pointerup",
+ "points": [{"x": 1.03, "y": 1.02, "identifier": 1}]},
+ {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointermove",
+ "points": [{"x": 0.97, "y": 1.01, "identifier": 1}]},
+ {"type": "pointerup",
+ "points": [{"x": 0.97, "y": 1.01, "identifier": 1}],
+ "removeGestureResolveDelay": true }
+ ],
+ "expectedGestures": [{ "type": "doubletap" }]
+ },
+ {
+ "events": [
+ {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}],
+ "removeGestureResolveDelay": true }
+ ],
+ "expectedGestures": [{ "type": "tripletap" }]
+ },
+ {
+ "events": [
+ {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}],
+ "removeDwellThreshold": true},
+ {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}
+ ],
+ "expectedGestures": [{ "type": "doubletaphold" },
+ { "type": "doubletapholdend" }]
+ },
+ {
+ "events": [
+ {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}],
+ "removeDwellThreshold": true},
+ {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}
+ ],
+ "expectedGestures": [{ "type": "taphold" }, { "type": "tapholdend" }]
+ },
+ {
+ "events": [
+ {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointermove", "points": [{"x": 1.5, "y": 1, "identifier": 1}]},
+ {"type": "pointerup", "points": [{"x": 1.5, "y": 1, "identifier": 1}]}
+ ],
+ "expectedGestures": [{ "type": "swiperight" }]
+ },
+ {
+ "events": [
+ {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointermove", "points": [{"x": 1.15, "y": 1, "identifier": 1}]},
+ {"type": "pointermove", "points": [{"x": 1.3, "y": 1, "identifier": 1}]},
+ {"type": "pointerup", "points": [{"x": 1.3, "y": 1, "identifier": 1}]}
+ ],
+ "expectedGestures": [{ "type": "swiperight" }]
+ },
+ {
+ "events": [
+ {"type": "pointerdown", "points": [{"x": 1.5, "y": 1, "identifier": 1}]},
+ {"type": "pointermove", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}
+ ],
+ "expectedGestures": [{ "type": "swipeleft" }]
+ },
+ {
+ "events": [
+ {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointermove", "points": [{"x": 1, "y": 1.5, "identifier": 1}]},
+ {"type": "pointerup", "points": [{"x": 1, "y": 1.5, "identifier": 1}]}
+ ],
+ "expectedGestures": [{ "type": "swipedown" }]
+ },
+ {
+ "events": [
+ {"type": "pointerdown", "points": [{"x": 1, "y": 1.5, "identifier": 1}]},
+ {"type": "pointermove", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}
+ ],
+ "expectedGestures": [{ "type": "swipeup" }]
+ },
+ {
+ "events": [
+ {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointermove",
+ "points": [{"x": 1.5, "y": 1.1, "identifier": 1}]},
+ {"type": "pointerup", "points": [{"x": 1.5, "y": 1.1, "identifier": 1}]}
+ ],
+ "expectedGestures": [{ "type": "swiperight" }]
+ },
+ {
+ "events": [
+ {"type": "pointerdown",
+ "points": [{"x": 1.5, "y": 1.1, "identifier": 1}]},
+ {"type": "pointermove", "points": [{"x": 1, "y": 0.95, "identifier": 1}]},
+ {"type": "pointerup", "points": [{"x": 1, "y": 0.95, "identifier": 1}]}
+ ],
+ "expectedGestures": [{ "type": "swipeleft" }]
+ },
+ {
+ "events": [
+ {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointermove",
+ "points": [{"x": 0.9, "y": 1.5, "identifier": 1}]},
+ {"type": "pointerup", "points": [{"x": 0.9, "y": 1.5, "identifier": 1}]}
+ ],
+ "expectedGestures": [{ "type": "swipedown" }]
+ },
+ {
+ "events": [
+ {"type": "pointerdown",
+ "points": [{"x": 1.1, "y": 1.5, "identifier": 1}]},
+ {"type": "pointermove", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}
+ ],
+ "expectedGestures": [{ "type": "swipeup" }]
+ },
+ {
+ "events": [
+ {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1},
+ {"x": 1, "y": 1.5, "identifier": 2}]},
+ {"type": "pointermove", "points": [{"x": 1.5, "y": 1, "identifier": 1},
+ {"x": 1.5, "y": 1.5, "identifier": 2}]},
+ {"type": "pointerup", "points": [{"x": 1.5, "y": 1, "identifier": 1},
+ {"x": 1.5, "y": 1.5, "identifier": 2}]}
+ ],
+ "expectedGestures": [{ "type": "swiperight", "fingers": 2 }]
+ },
+ {
+ "events": [
+ {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1},
+ {"x": 1, "y": 1.5, "identifier": 2}]},
+ {"type": "pointermove", "points": [{"x": 1.5, "y": 1, "identifier": 1},
+ {"x": 1.5, "y": 1.5, "identifier": 2}]},
+ {"type": "pointerup", "points": [{"x": 1.5, "y": 1, "identifier": 1}]},
+ {"type": "pointerup", "points": [{"x": 1.5, "y": 1.5, "identifier": 2}]}
+ ],
+ "expectedGestures": [{ "type": "swiperight", "fingers": 2 }]
+ },
+ {
+ "events": [
+ {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1},
+ {"x": 1, "y": 1.5, "identifier": 2},
+ {"x": 1, "y": 2, "identifier": 3}]},
+ {"type": "pointermove", "points": [{"x": 1.5, "y": 1, "identifier": 1},
+ {"x": 1.5, "y": 1.5, "identifier": 2},
+ {"x": 1.5, "y": 2, "identifier": 3}]},
+ {"type": "pointerup", "points": [{"x": 1.5, "y": 1, "identifier": 1},
+ {"x": 1.5, "y": 1.5, "identifier": 2},
+ {"x": 1.5, "y": 2, "identifier": 3}]}
+ ],
+ "expectedGestures": [{ "type": "swiperight", "fingers": 3 }]
+ },
+ {
+ "events": [
+ {"type": "pointerdown",
+ "points": [{"x": 1.6, "y": 1.5, "identifier": 1}],
+ "removeDwellThreshold": true},
+ {"type": "pointermove", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}
+ ],
+ "expectedGestures": [{ "type": "dwell" }, { "type": "explore" },
+ { "type": "exploreend" }]
+ },
+ {
+ "events": [
+ {"type": "pointerdown",
+ "points": [{"x": 1.6, "y": 1.5, "identifier": 1}],
+ "removeDwellThreshold": true},
+ {"type": "pointermove", "points": [{"x": 1, "y": 1, "identifier": 1}]},
+ {"type": "pointermove", "points": [{"x": 2, "y": 2, "identifier": 1}]},
+ {"type": "pointerup", "points": [{"x": 2, "y": 2, "identifier": 1}]}
+ ],
+ "expectedGestures": [{ "type": "dwell" }, { "type": "explore" },
+ { "type": "explore" }, { "type": "exploreend" }]
+ },
+ {
+ "events": [
+ {"type": "pointerdown",
+ "points": [{"x": 1.6, "y": 1.5, "identifier": 1}]},
+ {"type": "pointermove", "points": [{"x": 1, "y": 1, "identifier": 1}],
+ "removeSwipeMaxDuration": true},
+ {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}
+ ],
+ "expectedGestures": [{ "type": "explore" }, { "type": "exploreend" }]
+ },
+ {
+ "events": [
+ {"type": "pointerdown", "points": [{"x": 1, "y": 1.5, "identifier": 1}]},
+ {"type": "pointermove", "points": [{"x": 1, "y": 1, "identifier": 1}],
+ "removeSwipeMaxDuration": true},
+ {"type": "pointermove", "points": [{"x": 1.5, "y": 1, "identifier": 1}]},
+ {"type": "pointerup", "points": [{"x": 1.5, "y": 1, "identifier": 1}]}
+ ],
+ "expectedGestures": [{ "type": "explore" }, { "type": "explore" },
+ { "type": "exploreend" }]
+ },
+ {
+ "events": [
+ {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}],
+ "removeDwellThreshold": true},
+ {"type": "pointermove",
+ "points": [{"x": 1.5, "y": 1.5, "identifier": 1}]},
+ {"type": "pointermove",
+ "points": [{"x": 1.55, "y": 1.5, "identifier": 1}]},
+ {"type": "pointermove",
+ "points": [{"x": 1.6, "y": 1.5, "identifier": 1}]},
+ {"type": "pointermove",
+ "points": [{"x": 1.65, "y": 1.5, "identifier": 1}]},
+ {"type": "pointermove",
+ "points": [{"x": 1.7, "y": 1.5, "identifier": 1}]},
+ {"type": "pointermove",
+ "points": [{"x": 1.75, "y": 1.5, "identifier": 1}]},
+ {"type": "pointerup", "points": [{"x": 1.75, "y": 1.5, "identifier": 1}]}
+ ],
+ "expectedGestures": [{ "type": "dwell" }, { "type": "explore" },
+ { "type": "explore" }, { "type": "exploreend" }]
+ },
+ {
+ "events": [
+ {"type": "pointerdown", "points": [{"x": 0.075, "y": 1, "identifier": 1},
+ {"x": 1, "y": 1.5, "identifier": 2}]},
+ {"type": "pointermove", "points": [{"x": 1.5, "y": 1, "identifier": 1},
+ {"x": 1.5, "y": 1.5, "identifier": 2}]},
+ {"type": "pointerup", "points": [{"x": 1.5, "y": 1, "identifier": 1},
+ {"x": 1.5, "y": 1.5, "identifier": 2}]}
+ ],
+ "expectedGestures": [{ "type": "swiperight", "edge": true, "fingers": 2 }]
+ },
+ {
+ "title": "Bug 1182311 - 3 finger triple tap is not reliable 1/2",
+ "events": [
+ {"points": [
+ {"y": 1.88467, "x": 0.89311, "identifier": 0},
+ {"y": 2.78481, "x": 0.56259, "identifier": 1},
+ {"y": 1.35021, "x": 1.37834, "identifier": 2}], "type": "pointerdown"},
+ {"points": [
+ {"y": 1.88467, "x": 0.89311, "identifier": 0},
+ {"y": 2.78481, "x": 0.56259, "identifier": 1},
+ {"y": 1.35021, "x": 1.37834, "identifier": 2}], "type": "pointerup"},
+ {"points": [
+ {"y": 1.76512, "x": 0.98453, "identifier": 0},
+ {"y": 1.1744, "x": 1.4346, "identifier": 1},
+ {"y": 2.5879, "x": 0.61181, "identifier": 2}], "type": "pointerdown"},
+ {"points": [
+ {"y": 1.76512, "x": 0.98453, "identifier": 0},
+ {"y": 1.1744, "x": 1.4346, "identifier": 1},
+ {"y": 2.5879, "x": 0.61181, "identifier": 2}], "type": "pointerup"},
+ {"points": [
+ {"y": 1.30098, "x": 1.52602, "identifier": 0},
+ {"y": 1.94093, "x": 1.02672, "identifier": 1},
+ {"y": 2.67229, "x": 0.75246, "identifier": 2}], "type": "pointerdown"},
+ {"points": [
+ {"y": 1.30098, "x": 1.52602, "identifier": 0},
+ {"y": 1.94093, "x": 1.02672, "identifier": 1},
+ {"y": 2.67229, "x": 0.75246, "identifier": 2}], "type": "pointerup",
+ "removeGestureResolveDelay": true}],
+ "expectedGestures": [{ "type": "tripletap", "fingers": 3 }]
+ },
+ {
+ "title": "Bug 1182311 - 3 finger triple tap is not reliable 2/2",
+ "events": [
+ {"type": "pointerdown",
+ "points": [{"identifier": 0, "x": 2.21875, "y": 1.510417}]},
+ {"type": "pointerdown",
+ "points": [{"identifier": 1, "x": 1.479167, "y": 2.53125}]},
+ {"type": "pointerdown",
+ "points": [{"identifier": 2, "x": 1.072917, "y": 3.739583}]},
+ {"type": "pointermove",
+ "points": [{"identifier": 1, "x": 1.46875, "y": 2.53125}]},
+ {"type": "pointermove",
+ "points": [{"identifier": 1, "x": 1.447917, "y": 2.46875}]},
+ {"type": "pointerup",
+ "points": [{"identifier": 0, "x": 2.21875, "y": 1.510417}]},
+ {"type": "pointerup",
+ "points": [{"identifier": 1, "x": 1.447917, "y": 2.489583}]},
+ {"type": "pointerup",
+ "points": [{"identifier": 2, "x": 1.072917, "y": 3.739583}]},
+ {"type": "pointerdown",
+ "points": [{"identifier": 0, "x": 2.114583, "y": 1.572917}]},
+ {"type": "pointerdown",
+ "points": [{"identifier": 1, "x": 1.364583, "y": 2.614583}]},
+ {"type": "pointerdown",
+ "points": [{"identifier": 2, "x": 0.927083, "y": 3.864583}]},
+ {"type": "pointermove",
+ "points": [{"identifier": 1, "x": 1.364583, "y": 2.614583}]},
+ {"type": "pointermove",
+ "points": [{"identifier": 0, "x": 2.114583, "y": 1.572917}]},
+ {"type": "pointerup",
+ "points": [{"identifier": 1, "x": 1.364583, "y": 2.614583}]},
+ {"type": "pointerup",
+ "points": [{"identifier": 2, "x": 0.927083, "y": 3.864583}]},
+ {"type": "pointerup",
+ "points": [{"identifier": 0, "x": 2.114583, "y": 1.572917}]},
+ {"type": "pointerdown",
+ "points": [{"identifier": 0, "x": 1.4375, "y": 2.59375}]},
+ {"type": "pointerdown",
+ "points": [{"identifier": 1, "x": 1.083333, "y": 3.71875}]},
+ {"type": "pointerdown",
+ "points": [{"identifier": 2, "x": 2.15625, "y": 1.489583}]},
+ {"type": "pointermove",
+ "points": [{"identifier": 0, "x": 1.4375, "y": 2.59375},
+ {"identifier": 2, "x": 2.15625, "y": 1.489583}]},
+ {"type": "pointermove",
+ "points": [{"identifier": 0, "x": 1.4375, "y": 2.59375},
+ {"identifier": 2, "x": 2.15625, "y": 1.489583}]},
+ {"type": "pointerup",
+ "points": [{"identifier": 1, "x": 1.083333, "y": 3.71875}],
+ "removeGestureResolveDelay": true}
+ ],
+ "expectedGestures": [{ "type": "tripletap", "fingers": 3 }]
+ }
+
+]
diff --git a/accessible/tests/mochitest/jsat/jsatcommon.js b/accessible/tests/mochitest/jsat/jsatcommon.js
new file mode 100644
index 000000000..aa7ee74e4
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/jsatcommon.js
@@ -0,0 +1,739 @@
+// A common module to run tests on the AccessFu module
+
+'use strict';
+
+/*global isDeeply, getMainChromeWindow, SimpleTest, SpecialPowers, Logger,
+ AccessFu, Utils, addMessageListener, currentTabDocument, currentBrowser*/
+
+/**
+ * A global variable holding an array of test functions.
+ */
+var gTestFuncs = [];
+/**
+ * A global Iterator for the array of test functions.
+ */
+var gIterator;
+
+Components.utils.import('resource://gre/modules/Services.jsm');
+Components.utils.import("resource://gre/modules/accessibility/Utils.jsm");
+Components.utils.import("resource://gre/modules/accessibility/EventManager.jsm");
+Components.utils.import("resource://gre/modules/accessibility/Gestures.jsm");
+
+var AccessFuTest = {
+
+ addFunc: function AccessFuTest_addFunc(aFunc) {
+ if (aFunc) {
+ gTestFuncs.push(aFunc);
+ }
+ },
+
+ _registerListener: function AccessFuTest__registerListener(aWaitForMessage, aListenerFunc) {
+ var listener = {
+ observe: function observe(aMessage) {
+ // Ignore unexpected messages.
+ if (!(aMessage instanceof Components.interfaces.nsIConsoleMessage)) {
+ return;
+ }
+ if (aMessage.message.indexOf(aWaitForMessage) < 0) {
+ return;
+ }
+ aListenerFunc.apply(listener);
+ }
+ };
+ Services.console.registerListener(listener);
+ return listener;
+ },
+
+ on_log: function AccessFuTest_on_log(aWaitForMessage, aListenerFunc) {
+ return this._registerListener(aWaitForMessage, aListenerFunc);
+ },
+
+ off_log: function AccessFuTest_off_log(aListener) {
+ Services.console.unregisterListener(aListener);
+ },
+
+ once_log: function AccessFuTest_once_log(aWaitForMessage, aListenerFunc) {
+ return this._registerListener(aWaitForMessage,
+ function listenAndUnregister() {
+ Services.console.unregisterListener(this);
+ aListenerFunc();
+ });
+ },
+
+ _addObserver: function AccessFuTest__addObserver(aWaitForData, aListener) {
+ var listener = function listener(aSubject, aTopic, aData) {
+ var data = JSON.parse(aData)[1];
+ // Ignore non-relevant outputs.
+ if (!data) {
+ return;
+ }
+ isDeeply(data.details, aWaitForData, "Data is correct");
+ aListener.apply(listener);
+ };
+ Services.obs.addObserver(listener, 'accessibility-output', false);
+ return listener;
+ },
+
+ on: function AccessFuTest_on(aWaitForData, aListener) {
+ return this._addObserver(aWaitForData, aListener);
+ },
+
+ off: function AccessFuTest_off(aListener) {
+ Services.obs.removeObserver(aListener, 'accessibility-output');
+ },
+
+ once: function AccessFuTest_once(aWaitForData, aListener) {
+ return this._addObserver(aWaitForData, function observerAndRemove() {
+ Services.obs.removeObserver(this, 'accessibility-output');
+ aListener();
+ });
+ },
+
+ _waitForExplicitFinish: false,
+
+ waitForExplicitFinish: function AccessFuTest_waitForExplicitFinish() {
+ this._waitForExplicitFinish = true;
+ },
+
+ finish: function AccessFuTest_finish() {
+ // Disable the console service logging.
+ Logger.test = false;
+ Logger.logLevel = Logger.INFO;
+ // Reset Gesture Settings.
+ GestureSettings.dwellThreshold = this.dwellThreshold =
+ this.originalDwellThreshold;
+ GestureSettings.swipeMaxDuration = this.swipeMaxDuration =
+ this.originalSwipeMaxDuration;
+ GestureSettings.maxGestureResolveTimeout =
+ this.maxGestureResolveTimeout =
+ this.originalMaxGestureResolveTimeout;
+ // Finish through idle callback to let AccessFu._disable complete.
+ SimpleTest.executeSoon(function () {
+ AccessFu.detach();
+ SimpleTest.finish();
+ });
+ },
+
+ nextTest: function AccessFuTest_nextTest() {
+ var result = gIterator.next();
+ if (result.done) {
+ this.finish();
+ return;
+ }
+ var testFunc = result.value;
+ testFunc();
+ },
+
+ runTests: function AccessFuTest_runTests(aAdditionalPrefs) {
+ if (gTestFuncs.length === 0) {
+ ok(false, "No tests specified!");
+ SimpleTest.finish();
+ return;
+ }
+
+ // Create an Iterator for gTestFuncs array.
+ gIterator = (function*() {
+ for (var testFunc of gTestFuncs) {
+ yield testFunc;
+ }
+ })();
+
+ // Start AccessFu and put it in stand-by.
+ Components.utils.import("resource://gre/modules/accessibility/AccessFu.jsm");
+
+ AccessFu.attach(getMainChromeWindow(window));
+
+ AccessFu.readyCallback = function readyCallback() {
+ // Enable logging to the console service.
+ Logger.test = true;
+ Logger.logLevel = Logger.DEBUG;
+ };
+
+ var prefs = [['accessibility.accessfu.notify_output', 1],
+ ['dom.mozSettings.enabled', true]];
+ prefs.push.apply(prefs, aAdditionalPrefs);
+
+ this.originalDwellThreshold = GestureSettings.dwellThreshold;
+ this.originalSwipeMaxDuration = GestureSettings.swipeMaxDuration;
+ this.originalMaxGestureResolveTimeout =
+ GestureSettings.maxGestureResolveTimeout;
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1001945 - sometimes
+ // SimpleTest.executeSoon timeout is bigger than the timer settings in
+ // GestureSettings that causes intermittents.
+ this.dwellThreshold = GestureSettings.dwellThreshold =
+ GestureSettings.dwellThreshold * 10;
+ this.swipeMaxDuration = GestureSettings.swipeMaxDuration =
+ GestureSettings.swipeMaxDuration * 10;
+ this.maxGestureResolveTimeout = GestureSettings.maxGestureResolveTimeout =
+ GestureSettings.maxGestureResolveTimeout * 10;
+
+ SpecialPowers.pushPrefEnv({ 'set': prefs }, function () {
+ if (AccessFuTest._waitForExplicitFinish) {
+ // Run all test functions asynchronously.
+ AccessFuTest.nextTest();
+ } else {
+ // Run all test functions synchronously.
+ gTestFuncs.forEach(testFunc => testFunc());
+ AccessFuTest.finish();
+ }
+ });
+ }
+};
+
+function AccessFuContentTest(aFuncResultPairs) {
+ this.queue = aFuncResultPairs;
+}
+
+AccessFuContentTest.prototype = {
+ expected: [],
+ currentAction: null,
+ actionNum: -1,
+
+ start: function(aFinishedCallback) {
+ Logger.logLevel = Logger.DEBUG;
+ this.finishedCallback = aFinishedCallback;
+ var self = this;
+
+ // Get top content message manager, and set it up.
+ this.mms = [Utils.getMessageManager(currentBrowser())];
+ this.setupMessageManager(this.mms[0], function () {
+ // Get child message managers and set them up
+ var frames = currentTabDocument().querySelectorAll('iframe');
+ if (frames.length === 0) {
+ self.pump();
+ return;
+ }
+
+ var toSetup = 0;
+ for (var i = 0; i < frames.length; i++ ) {
+ var mm = Utils.getMessageManager(frames[i]);
+ if (mm) {
+ toSetup++;
+ self.mms.push(mm);
+ self.setupMessageManager(mm, function () {
+ if (--toSetup === 0) {
+ // All message managers are loaded and ready to go.
+ self.pump();
+ }
+ });
+ }
+ }
+ });
+ },
+
+ finish: function() {
+ Logger.logLevel = Logger.INFO;
+ for (var mm of this.mms) {
+ mm.sendAsyncMessage('AccessFu:Stop');
+ mm.removeMessageListener('AccessFu:Present', this);
+ mm.removeMessageListener('AccessFu:Input', this);
+ mm.removeMessageListener('AccessFu:CursorCleared', this);
+ mm.removeMessageListener('AccessFu:Focused', this);
+ mm.removeMessageListener('AccessFu:AriaHidden', this);
+ mm.removeMessageListener('AccessFu:Ready', this);
+ mm.removeMessageListener('AccessFu:ContentStarted', this);
+ }
+ if (this.finishedCallback) {
+ this.finishedCallback();
+ }
+ },
+
+ setupMessageManager: function (aMessageManager, aCallback) {
+ function contentScript() {
+ addMessageListener('AccessFuTest:Focus', function (aMessage) {
+ var elem = content.document.querySelector(aMessage.json.selector);
+ if (elem) {
+ if (aMessage.json.blur) {
+ elem.blur();
+ } else {
+ elem.focus();
+ }
+ }
+ });
+ }
+
+ aMessageManager.addMessageListener('AccessFu:Present', this);
+ aMessageManager.addMessageListener('AccessFu:Input', this);
+ aMessageManager.addMessageListener('AccessFu:CursorCleared', this);
+ aMessageManager.addMessageListener('AccessFu:Focused', this);
+ aMessageManager.addMessageListener('AccessFu:AriaHidden', this);
+ aMessageManager.addMessageListener('AccessFu:Ready', function () {
+ aMessageManager.addMessageListener('AccessFu:ContentStarted', aCallback);
+ aMessageManager.sendAsyncMessage('AccessFu:Start',
+ { buildApp: 'browser',
+ androidSdkVersion: Utils.AndroidSdkVersion,
+ logLevel: 'DEBUG',
+ inTest: true });
+ });
+
+ aMessageManager.loadFrameScript(
+ 'chrome://global/content/accessibility/content-script.js', false);
+ aMessageManager.loadFrameScript(
+ 'data:,(' + contentScript.toString() + ')();', false);
+ },
+
+ pump: function() {
+ this.expected.shift();
+ if (this.expected.length) {
+ return;
+ }
+
+ var currentPair = this.queue.shift();
+
+ if (currentPair) {
+ this.actionNum++;
+ this.currentAction = currentPair[0];
+ if (typeof this.currentAction === 'function') {
+ this.currentAction(this.mms[0]);
+ } else if (this.currentAction) {
+ this.mms[0].sendAsyncMessage(this.currentAction.name,
+ this.currentAction.json);
+ }
+
+ this.expected = currentPair.slice(1, currentPair.length);
+
+ if (!this.expected[0]) {
+ this.pump();
+ }
+ } else {
+ this.finish();
+ }
+ },
+
+ receiveMessage: function(aMessage) {
+ var expected = this.expected[0];
+
+ if (!expected) {
+ return;
+ }
+
+ var actionsString = typeof this.currentAction === 'function' ?
+ this.currentAction.name + '()' : JSON.stringify(this.currentAction);
+
+ if (typeof expected === 'string') {
+ ok(true, 'Got ' + expected + ' after ' + actionsString);
+ this.pump();
+ } else if (expected.ignore && !expected.ignore(aMessage)) {
+ expected.is(aMessage.json, 'after ' + actionsString +
+ ' (' + this.actionNum + ')');
+ expected.is_correct_focus();
+ this.pump();
+ }
+ }
+};
+
+// Common content messages
+
+var ContentMessages = {
+ simpleMoveFirst: {
+ name: 'AccessFu:MoveCursor',
+ json: {
+ action: 'moveFirst',
+ rule: 'Simple',
+ inputType: 'gesture',
+ origin: 'top'
+ }
+ },
+
+ simpleMoveLast: {
+ name: 'AccessFu:MoveCursor',
+ json: {
+ action: 'moveLast',
+ rule: 'Simple',
+ inputType: 'gesture',
+ origin: 'top'
+ }
+ },
+
+ simpleMoveNext: {
+ name: 'AccessFu:MoveCursor',
+ json: {
+ action: 'moveNext',
+ rule: 'Simple',
+ inputType: 'gesture',
+ origin: 'top'
+ }
+ },
+
+ simpleMovePrevious: {
+ name: 'AccessFu:MoveCursor',
+ json: {
+ action: 'movePrevious',
+ rule: 'Simple',
+ inputType: 'gesture',
+ origin: 'top'
+ }
+ },
+
+ clearCursor: {
+ name: 'AccessFu:ClearCursor',
+ json: {
+ origin: 'top'
+ }
+ },
+
+ moveOrAdjustUp: function moveOrAdjustUp(aRule) {
+ return {
+ name: 'AccessFu:MoveCursor',
+ json: {
+ origin: 'top',
+ action: 'movePrevious',
+ inputType: 'gesture',
+ rule: (aRule || 'Simple'),
+ adjustRange: true
+ }
+ }
+ },
+
+ moveOrAdjustDown: function moveOrAdjustUp(aRule) {
+ return {
+ name: 'AccessFu:MoveCursor',
+ json: {
+ origin: 'top',
+ action: 'moveNext',
+ inputType: 'gesture',
+ rule: (aRule || 'Simple'),
+ adjustRange: true
+ }
+ }
+ },
+
+ androidScrollForward: function adjustUp() {
+ return {
+ name: 'AccessFu:AndroidScroll',
+ json: { origin: 'top', direction: 'forward' }
+ };
+ },
+
+ androidScrollBackward: function adjustDown() {
+ return {
+ name: 'AccessFu:AndroidScroll',
+ json: { origin: 'top', direction: 'backward' }
+ };
+ },
+
+ focusSelector: function focusSelector(aSelector, aBlur) {
+ return {
+ name: 'AccessFuTest:Focus',
+ json: {
+ selector: aSelector,
+ blur: aBlur
+ }
+ };
+ },
+
+ activateCurrent: function activateCurrent(aOffset) {
+ return {
+ name: 'AccessFu:Activate',
+ json: {
+ origin: 'top',
+ offset: aOffset
+ }
+ };
+ },
+
+ moveNextBy: function moveNextBy(aGranularity) {
+ return {
+ name: 'AccessFu:MoveByGranularity',
+ json: {
+ direction: 'Next',
+ granularity: this._granularityMap[aGranularity]
+ }
+ };
+ },
+
+ movePreviousBy: function movePreviousBy(aGranularity) {
+ return {
+ name: 'AccessFu:MoveByGranularity',
+ json: {
+ direction: 'Previous',
+ granularity: this._granularityMap[aGranularity]
+ }
+ };
+ },
+
+ moveCaretNextBy: function moveCaretNextBy(aGranularity) {
+ return {
+ name: 'AccessFu:MoveCaret',
+ json: {
+ direction: 'Next',
+ granularity: this._granularityMap[aGranularity]
+ }
+ };
+ },
+
+ moveCaretPreviousBy: function moveCaretPreviousBy(aGranularity) {
+ return {
+ name: 'AccessFu:MoveCaret',
+ json: {
+ direction: 'Previous',
+ granularity: this._granularityMap[aGranularity]
+ }
+ };
+ },
+
+ _granularityMap: {
+ 'character': 1, // MOVEMENT_GRANULARITY_CHARACTER
+ 'word': 2, // MOVEMENT_GRANULARITY_WORD
+ 'paragraph': 8 // MOVEMENT_GRANULARITY_PARAGRAPH
+ }
+};
+
+function ExpectedMessage (aName, aOptions) {
+ this.name = aName;
+ this.options = aOptions || {};
+ this.json = {};
+}
+
+ExpectedMessage.prototype.lazyCompare = function(aReceived, aExpected, aInfo) {
+ if (aExpected && !aReceived) {
+ return [false, 'Expected something but got nothing -- ' + aInfo];
+ }
+
+ var matches = true;
+ var delta = [];
+ for (var attr in aExpected) {
+ var expected = aExpected[attr];
+ var received = aReceived[attr];
+ if (typeof expected === 'object') {
+ var [childMatches, childDelta] = this.lazyCompare(received, expected);
+ if (!childMatches) {
+ delta.push(attr + ' [ ' + childDelta + ' ]');
+ matches = false;
+ }
+ } else {
+ if (received !== expected) {
+ delta.push(
+ attr + ' [ expected ' + JSON.stringify(expected) +
+ ' got ' + JSON.stringify(received) + ' ]');
+ matches = false;
+ }
+ }
+ }
+
+ var msg = delta.length ? delta.join(' ') : 'Structures lazily match';
+ return [matches, msg + ' -- ' + aInfo];
+};
+
+ExpectedMessage.prototype.is = function(aReceived, aInfo) {
+ var checkFunc = this.options.todo ? 'todo' : 'ok';
+ SimpleTest[checkFunc].apply(
+ SimpleTest, this.lazyCompare(aReceived, this.json, aInfo));
+};
+
+ExpectedMessage.prototype.is_correct_focus = function(aInfo) {
+ if (!this.options.focused) {
+ return;
+ }
+
+ var checkFunc = this.options.focused_todo ? 'todo_is' : 'is';
+ var doc = currentTabDocument();
+ SimpleTest[checkFunc].apply(SimpleTest,
+ [ doc.activeElement, doc.querySelector(this.options.focused),
+ 'Correct element is focused: ' + this.options.focused + ' -- ' + aInfo ]);
+};
+
+ExpectedMessage.prototype.ignore = function(aMessage) {
+ return aMessage.name !== this.name;
+};
+
+function ExpectedPresent(aB2g, aAndroid, aOptions) {
+ ExpectedMessage.call(this, 'AccessFu:Present', aOptions);
+ if (aB2g) {
+ this.json.b2g = aB2g;
+ }
+
+ if (aAndroid) {
+ this.json.android = aAndroid;
+ }
+}
+
+ExpectedPresent.prototype = Object.create(ExpectedMessage.prototype);
+
+ExpectedPresent.prototype.is = function(aReceived, aInfo) {
+ var received = this.extract_presenters(aReceived);
+
+ for (var presenter of ['b2g', 'android']) {
+ if (!this.options['no_' + presenter]) {
+ var todo = this.options.todo || this.options[presenter + '_todo']
+ SimpleTest[todo ? 'todo' : 'ok'].apply(
+ SimpleTest, this.lazyCompare(received[presenter],
+ this.json[presenter], aInfo + ' (' + presenter + ')'));
+ }
+ }
+};
+
+ExpectedPresent.prototype.extract_presenters = function(aReceived) {
+ var received = { count: 0 };
+ for (var presenter of aReceived) {
+ if (presenter) {
+ received[presenter.type.toLowerCase()] = presenter.details;
+ received.count++;
+ }
+ }
+
+ return received
+};
+
+ExpectedPresent.prototype.ignore = function(aMessage) {
+ if (ExpectedMessage.prototype.ignore.call(this, aMessage)) {
+ return true;
+ }
+
+ var received = this.extract_presenters(aMessage.json);
+ return received.count === 0 ||
+ (received.visual && received.visual.eventType === 'viewport-change') ||
+ (received.android &&
+ received.android[0].eventType === AndroidEvent.VIEW_SCROLLED);
+};
+
+function ExpectedCursorChange(aSpeech, aOptions) {
+ ExpectedPresent.call(this, {
+ eventType: 'vc-change',
+ data: aSpeech
+ }, [{
+ eventType: 0x8000, // VIEW_ACCESSIBILITY_FOCUSED
+ }], aOptions);
+}
+
+ExpectedCursorChange.prototype = Object.create(ExpectedPresent.prototype);
+
+function ExpectedCursorTextChange(aSpeech, aStartOffset, aEndOffset, aOptions) {
+ ExpectedPresent.call(this, {
+ eventType: 'vc-change',
+ data: aSpeech
+ }, [{
+ eventType: AndroidEvent.VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
+ fromIndex: aStartOffset,
+ toIndex: aEndOffset
+ }], aOptions);
+
+ // bug 980509
+ this.options.b2g_todo = true;
+}
+
+ExpectedCursorTextChange.prototype =
+ Object.create(ExpectedCursorChange.prototype);
+
+function ExpectedClickAction(aOptions) {
+ ExpectedPresent.call(this, {
+ eventType: 'action',
+ data: [{ string: 'clickAction' }]
+ }, [{
+ eventType: AndroidEvent.VIEW_CLICKED
+ }], aOptions);
+}
+
+ExpectedClickAction.prototype = Object.create(ExpectedPresent.prototype);
+
+function ExpectedCheckAction(aChecked, aOptions) {
+ ExpectedPresent.call(this, {
+ eventType: 'action',
+ data: [{ string: aChecked ? 'checkAction' : 'uncheckAction' }]
+ }, [{
+ eventType: AndroidEvent.VIEW_CLICKED,
+ checked: aChecked
+ }], aOptions);
+}
+
+ExpectedCheckAction.prototype = Object.create(ExpectedPresent.prototype);
+
+function ExpectedSwitchAction(aSwitched, aOptions) {
+ ExpectedPresent.call(this, {
+ eventType: 'action',
+ data: [{ string: aSwitched ? 'onAction' : 'offAction' }]
+ }, [{
+ eventType: AndroidEvent.VIEW_CLICKED,
+ checked: aSwitched
+ }], aOptions);
+}
+
+ExpectedSwitchAction.prototype = Object.create(ExpectedPresent.prototype);
+
+function ExpectedNameChange(aName, aOptions) {
+ ExpectedPresent.call(this, {
+ eventType: 'name-change',
+ data: aName
+ }, null, aOptions);
+}
+
+ExpectedNameChange.prototype = Object.create(ExpectedPresent.prototype);
+
+function ExpectedValueChange(aValue, aOptions) {
+ ExpectedPresent.call(this, {
+ eventType: 'value-change',
+ data: aValue
+ }, null, aOptions);
+}
+
+ExpectedValueChange.prototype = Object.create(ExpectedPresent.prototype);
+
+function ExpectedTextChanged(aValue, aOptions) {
+ ExpectedPresent.call(this, {
+ eventType: 'text-change',
+ data: aValue
+ }, null, aOptions);
+}
+
+ExpectedTextChanged.prototype = Object.create(ExpectedPresent.prototype);
+
+function ExpectedEditState(aEditState, aOptions) {
+ ExpectedMessage.call(this, 'AccessFu:Input', aOptions);
+ this.json = aEditState;
+}
+
+ExpectedEditState.prototype = Object.create(ExpectedMessage.prototype);
+
+function ExpectedTextSelectionChanged(aStart, aEnd, aOptions) {
+ ExpectedPresent.call(this, null, [{
+ eventType: AndroidEvent.VIEW_TEXT_SELECTION_CHANGED,
+ brailleOutput: {
+ selectionStart: aStart,
+ selectionEnd: aEnd
+ }}], aOptions);
+}
+
+ExpectedTextSelectionChanged.prototype =
+ Object.create(ExpectedPresent.prototype);
+
+function ExpectedTextCaretChanged(aFrom, aTo, aOptions) {
+ ExpectedPresent.call(this, null, [{
+ eventType: AndroidEvent.VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
+ fromIndex: aFrom,
+ toIndex: aTo
+ }], aOptions);
+}
+
+ExpectedTextCaretChanged.prototype = Object.create(ExpectedPresent.prototype);
+
+function ExpectedAnnouncement(aAnnouncement, aOptions) {
+ ExpectedPresent.call(this, null, [{
+ eventType: AndroidEvent.ANNOUNCEMENT,
+ text: [ aAnnouncement],
+ addedCount: aAnnouncement.length
+ }], aOptions);
+}
+
+ExpectedAnnouncement.prototype = Object.create(ExpectedPresent.prototype);
+
+function ExpectedNoMove(aOptions) {
+ ExpectedPresent.call(this, {eventType: 'no-move' }, null, aOptions);
+}
+
+ExpectedNoMove.prototype = Object.create(ExpectedPresent.prototype);
+
+var AndroidEvent = {
+ VIEW_CLICKED: 0x01,
+ VIEW_LONG_CLICKED: 0x02,
+ VIEW_SELECTED: 0x04,
+ VIEW_FOCUSED: 0x08,
+ VIEW_TEXT_CHANGED: 0x10,
+ WINDOW_STATE_CHANGED: 0x20,
+ VIEW_HOVER_ENTER: 0x80,
+ VIEW_HOVER_EXIT: 0x100,
+ VIEW_SCROLLED: 0x1000,
+ VIEW_TEXT_SELECTION_CHANGED: 0x2000,
+ ANNOUNCEMENT: 0x4000,
+ VIEW_ACCESSIBILITY_FOCUSED: 0x8000,
+ VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY: 0x20000
+};
diff --git a/accessible/tests/mochitest/jsat/output.js b/accessible/tests/mochitest/jsat/output.js
new file mode 100644
index 000000000..5afcc8a66
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/output.js
@@ -0,0 +1,114 @@
+var Cu = Components.utils;
+const PREF_UTTERANCE_ORDER = "accessibility.accessfu.utterance";
+
+Cu.import('resource://gre/modules/accessibility/Utils.jsm');
+Cu.import("resource://gre/modules/accessibility/OutputGenerator.jsm", this);
+
+/**
+ * Test context output generation.
+ *
+ * @param expected {Array} expected output.
+ * @param aAccOrElmOrID identifier to get an accessible to test.
+ * @param aOldAccOrElmOrID optional identifier to get an accessible relative to
+ * the |aAccOrElmOrID|.
+ * @param aGenerator the output generator to use when generating accessible
+ * output
+ *
+ * Note: if |aOldAccOrElmOrID| is not provided, the |aAccOrElmOrID| must be
+ * scoped to the "root" element in markup.
+ */
+function testContextOutput(expected, aAccOrElmOrID, aOldAccOrElmOrID, aGenerator) {
+ var accessible = getAccessible(aAccOrElmOrID);
+ var oldAccessible = aOldAccOrElmOrID !== null ?
+ getAccessible(aOldAccOrElmOrID || 'root') : null;
+ var context = new PivotContext(accessible, oldAccessible);
+ var output = aGenerator.genForContext(context);
+
+ // Create a version of the output that has null members where we have
+ // null members in the expected output. Those are indexes that are not testable
+ // because of the changing nature of the test (different window names), or strings
+ // that are inaccessible to us, like the title of parent documents.
+ var masked_output = [];
+ for (var i=0; i < output.length; i++) {
+ if (expected[i] === null) {
+ masked_output.push(null);
+ } else {
+ masked_output[i] = typeof output[i] === "string" ? output[i].trim() :
+ output[i];
+ }
+ }
+
+ isDeeply(masked_output, expected,
+ "Context output is correct for " + aAccOrElmOrID +
+ " (output: " + JSON.stringify(output) + ") ==" +
+ " (expected: " + JSON.stringify(expected) + ")");
+}
+
+/**
+ * Test object output generated array that includes names.
+ * Note: test ignores outputs without the name.
+ *
+ * @param aAccOrElmOrID identifier to get an accessible to test.
+ * @param aGenerator the output generator to use when generating accessible
+ * output
+ */
+function testObjectOutput(aAccOrElmOrID, aGenerator) {
+ var accessible = getAccessible(aAccOrElmOrID);
+ if (!accessible.name || !accessible.name.trim()) {
+ return;
+ }
+ var context = new PivotContext(accessible);
+ var output = aGenerator.genForObject(accessible, context);
+ var outputOrder;
+ try {
+ outputOrder = SpecialPowers.getIntPref(PREF_UTTERANCE_ORDER);
+ } catch (ex) {
+ // PREF_UTTERANCE_ORDER not set.
+ outputOrder = 0;
+ }
+ var expectedNameIndex = outputOrder === 0 ? output.length - 1 : 0;
+ var nameIndex = output.indexOf(accessible.name);
+
+ if (nameIndex > -1) {
+ ok(output.indexOf(accessible.name) === expectedNameIndex,
+ "Object output is correct for " + aAccOrElmOrID);
+ }
+}
+
+/**
+ * Test object and context output for an accessible.
+ *
+ * @param expected {Array} expected output.
+ * @param aAccOrElmOrID identifier to get an accessible to test.
+ * @param aOldAccOrElmOrID optional identifier to get an accessible relative to
+ * the |aAccOrElmOrID|.
+ * @param aOutputKind the type of output
+ */
+function testOutput(expected, aAccOrElmOrID, aOldAccOrElmOrID, aOutputKind) {
+ var generator;
+ if (aOutputKind === 1) {
+ generator = UtteranceGenerator;
+ } else {
+ generator = BrailleGenerator;
+ }
+ testContextOutput(expected, aAccOrElmOrID, aOldAccOrElmOrID, generator);
+ // Just need to test object output for individual
+ // accOrElmOrID.
+ if (aOldAccOrElmOrID) {
+ return;
+ }
+ testObjectOutput(aAccOrElmOrID, generator);
+}
+
+function testHints(expected, aAccOrElmOrID, aOldAccOrElmOrID) {
+ var accessible = getAccessible(aAccOrElmOrID);
+ var oldAccessible = aOldAccOrElmOrID !== null ?
+ getAccessible(aOldAccOrElmOrID || 'root') : null;
+ var context = new PivotContext(accessible, oldAccessible);
+ var hints = context.interactionHints;
+
+ isDeeply(hints, expected,
+ "Context hitns are correct for " + aAccOrElmOrID +
+ " (hints: " + JSON.stringify(hints) + ") ==" +
+ " (expected: " + JSON.stringify(expected) + ")");
+}
diff --git a/accessible/tests/mochitest/jsat/test_alive.html b/accessible/tests/mochitest/jsat/test_alive.html
new file mode 100644
index 000000000..cd4eef712
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/test_alive.html
@@ -0,0 +1,81 @@
+<html>
+
+<head>
+ <title>AccessFu test for enabling</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="./jsatcommon.js"></script>
+ <script type="application/javascript">
+
+ function prefStart() {
+ AccessFuTest.once_log("AccessFu:Enabled", () =>
+ ok(AccessFu._enabled, "AccessFu was enabled again."));
+ AccessFuTest.once_log("EventManager.start", AccessFuTest.nextTest);
+ // Start AccessFu via pref.
+ SpecialPowers.pushPrefEnv({"set": [['accessibility.accessfu.activate', 1]]});
+ }
+
+ // Listen for 'EventManager.stop' and enable AccessFu again.
+ function settingsStart() {
+ isnot(AccessFu._enabled, true, "AccessFu was disabled.");
+ // XXX: Bug 978076 - test start with SettingsManager.
+ //navigator.mozSettings.createLock().set(
+ // {'accessibility.screenreader': false});
+ AccessFuTest.once_log("EventManager.start", () => {
+ ok(AccessFu._enabled, "AccessFu was enabled again.");
+ AccessFuTest.nextTest();
+ });
+ AccessFu._enable();
+ }
+
+ // Make sure EventManager is started again.
+ function settingsStop() {
+ ok(AccessFu._enabled, "AccessFu was enabled again.");
+ // XXX: Bug 978076 - test stop with SettingsManager.
+ //navigator.mozSettings.createLock().set(
+ // {'accessibility.screenreader': false});
+ AccessFuTest.once_log("EventManager.stop", () => {
+ isnot(AccessFu._enabled, "AccessFu was disabled.");
+ AccessFuTest.finish();
+ });
+ AccessFu._disable();
+ }
+
+ // Listen for initial 'EventManager.start' and disable AccessFu.
+ function prefStop() {
+ ok(AccessFu._enabled, "AccessFu was started via preference.");
+ AccessFuTest.once_log("AccessFu:Disabled", () =>
+ isnot(AccessFu._enabled, true, "AccessFu was disabled."));
+ AccessFuTest.once_log("EventManager.stop", AccessFuTest.nextTest);
+
+ SpecialPowers.pushPrefEnv({"set": [['accessibility.accessfu.activate', 0]]});
+ }
+
+ function doTest() {
+ AccessFuTest.addFunc(prefStart);
+ AccessFuTest.addFunc(prefStop);
+ AccessFuTest.addFunc(settingsStart);
+ AccessFuTest.addFunc(settingsStop);
+ AccessFuTest.waitForExplicitFinish();
+ AccessFuTest.runTests(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=811307"
+ title="[AccessFu] Add mochitest for enabling">
+ Mozilla Bug 811307
+ </a>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/jsat/test_content_integration.html b/accessible/tests/mochitest/jsat/test_content_integration.html
new file mode 100644
index 000000000..809f79726
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/test_content_integration.html
@@ -0,0 +1,343 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Tests AccessFu content integration</title>
+ <meta charset="utf-8" />
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/chrome-harness.js">
+ </script>
+
+ <script type="application/javascript" src="../common.js"></script>
+ <script type="application/javascript" src="../browser.js"></script>
+ <script type="application/javascript" src="../events.js"></script>
+ <script type="application/javascript" src="../role.js"></script>
+ <script type="application/javascript" src="../states.js"></script>
+ <script type="application/javascript" src="../layout.js"></script>
+ <script type="application/javascript" src="jsatcommon.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ var doc = currentTabDocument();
+ var iframe = doc.createElement('iframe');
+ iframe.id = 'iframe';
+ iframe.mozbrowser = true;
+ iframe.addEventListener('mozbrowserloadend', function () {
+ var contentTest = new AccessFuContentTest(
+ [
+ // Simple traversal forward
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(
+ ['Traversal Rule test document', 'Phone status bar'],
+ { focused: 'body' })],
+ [ContentMessages.simpleMovePrevious, new ExpectedNoMove()],
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(["Back", {"string": "pushbutton"}])],
+ [ContentMessages.simpleMoveNext, new ExpectedCursorChange(
+ ['such app', 'wow', {'string': 'headingLevel', 'args': [1]}],
+ { focused: 'iframe' })],
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(['many option', {'string': 'stateNotChecked'},
+ {'string': 'checkbutton'}, {'string': 'listStart'},
+ {'string': 'list'}, {'string': 'listItemsCount', 'count': 1}])],
+
+ // check checkbox
+ [ContentMessages.activateCurrent(),
+ new ExpectedClickAction({ no_android: true }),
+ new ExpectedCheckAction(true)],
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(['much range', {'string': 'label'}])],
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(['much range', '5', {'string': 'slider'}])],
+ [ContentMessages.moveOrAdjustUp(), new ExpectedValueChange('6')],
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])],
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(['apple', {'string': 'pushbutton'}])],
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(['Light', {"string": "stateOff"}, {'string': 'switch'}])],
+ // switch on
+ [ContentMessages.activateCurrent(),
+ new ExpectedClickAction({ no_android: true }),
+ new ExpectedSwitchAction(true)],
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(['slider', '0', {'string': 'slider'}])],
+
+ // Simple traversal backward
+ [ContentMessages.simpleMovePrevious,
+ new ExpectedCursorChange(['Light', {"string": "stateOn"}, {'string': 'switch'}])],
+ // switch off
+ [ContentMessages.activateCurrent(),
+ new ExpectedClickAction({ no_android: true }),
+ new ExpectedSwitchAction(false)],
+ [ContentMessages.simpleMovePrevious,
+ new ExpectedCursorChange(['apple', {'string': 'pushbutton'}])],
+ [ContentMessages.simpleMovePrevious,
+ new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])],
+ [ContentMessages.simpleMovePrevious,
+ new ExpectedCursorChange(['such app', 'much range', '6', {'string': 'slider'}])],
+ [ContentMessages.moveOrAdjustDown(), new ExpectedValueChange('5')],
+ [ContentMessages.androidScrollForward(), new ExpectedValueChange('6')],
+ [ContentMessages.androidScrollBackward(), new ExpectedValueChange('5')],
+ [ContentMessages.simpleMovePrevious,
+ new ExpectedCursorChange(['much range', {'string': 'label'}])],
+ [ContentMessages.simpleMovePrevious,
+ new ExpectedCursorChange(['many option', {'string': 'stateChecked'},
+ {'string': 'checkbutton'}, {'string': 'listStart'},
+ {'string': 'list'}, {'string': 'listItemsCount', 'count': 1}])],
+ // uncheck checkbox
+ [ContentMessages.activateCurrent(),
+ new ExpectedClickAction({ no_android: true }),
+ new ExpectedCheckAction(false)],
+ [ContentMessages.simpleMovePrevious,
+ new ExpectedCursorChange(['wow', {'string': 'headingLevel', 'args': [1]}])],
+ [ContentMessages.simpleMovePrevious,
+ new ExpectedCursorChange(["Back", {"string": "pushbutton"}])],
+ [ContentMessages.simpleMovePrevious,
+ new ExpectedCursorChange(['Phone status bar'])],
+
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(["Back", {"string": "pushbutton"}])],
+ // Moving to the absolute last item from an embedded document
+ // fails. Bug 972035.
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(
+ ['such app', 'wow', {'string': 'headingLevel', 'args': [1]}])],
+ // Move from an inner frame to the last element in the parent doc
+ [ContentMessages.simpleMoveLast,
+ new ExpectedCursorChange(
+ ['slider', '0', {'string': 'slider'}], { b2g_todo: true })],
+
+ [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
+
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])],
+ [ContentMessages.moveOrAdjustDown('FormElement'),
+ new ExpectedCursorChange(['Back', {"string": "pushbutton"}])],
+ [ContentMessages.moveOrAdjustDown('FormElement'),
+ new ExpectedCursorChange(['such app', 'many option', {'string': 'stateNotChecked'},
+ {'string': 'checkbutton'}, {'string': 'listStart'},
+ {'string': 'list'}, {'string': 'listItemsCount', 'count': 1}])],
+ [ContentMessages.moveOrAdjustDown('FormElement'),
+ new ExpectedCursorChange(['much range', '5', {'string': 'slider'}])],
+ // Calling AdjustOrMove should adjust the range.
+ [ContentMessages.moveOrAdjustDown('FormElement'),
+ new ExpectedValueChange('4')],
+ [ContentMessages.moveOrAdjustUp('FormElement'),
+ new ExpectedValueChange('5')],
+ [ContentMessages.simpleMovePrevious,
+ new ExpectedCursorChange(['much range', {'string': 'label'}])],
+ [ContentMessages.moveOrAdjustUp('FormElement'),
+ new ExpectedCursorChange(['many option', {'string': 'stateNotChecked'},
+ {'string': 'checkbutton'}, {'string': 'listStart'},
+ {'string': 'list'}, {'string': 'listItemsCount', 'count': 1}])],
+ [ContentMessages.moveOrAdjustUp('FormElement'),
+ new ExpectedCursorChange(['Back', {"string": "pushbutton"}])],
+
+ [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
+
+ // Moving to the absolute first item from an embedded document
+ // fails. Bug 972035.
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])],
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(["Back", {"string": "pushbutton"}])],
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(['such app', 'wow', {'string': 'headingLevel', 'args': [1]}])],
+ [ContentMessages.simpleMoveNext, new ExpectedCursorChange(
+ ['many option', {'string': 'stateNotChecked'},
+ {'string': 'checkbutton'}, {'string': 'listStart'},
+ {'string': 'list'}, {'string': 'listItemsCount', 'count': 1}])],
+ [ContentMessages.simpleMoveFirst,
+ new ExpectedCursorChange(['Phone status bar'], { b2g_todo: true })],
+
+ // Reset cursors
+ [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
+
+ // Current virtual cursor's position's name changes
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])],
+ [ContentMessages.focusSelector('button#fruit', false),
+ new ExpectedCursorChange(['apple', {'string': 'pushbutton'}])],
+ [doc.defaultView.renameFruit, new ExpectedNameChange('banana')],
+
+ // Name and value changes inside a live-region (no cursor present)
+ [doc.defaultView.renameSlider,
+ new ExpectedNameChange('mover')],
+ [doc.defaultView.changeSliderValue,
+ new ExpectedValueChange('medium')],
+
+ // Blur button and reset cursor
+ [ContentMessages.focusSelector('button#fruit', true), null],
+ [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
+
+ // Move cursor with focus in outside document
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])],
+ [ContentMessages.focusSelector('button#home', false),
+ new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])],
+
+ // Blur button and reset cursor
+ [ContentMessages.focusSelector('button#home', true), null],
+ [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
+
+ // Set focus on element outside of embedded frame while
+ // cursor is in frame
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])],
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(["Back", {"string": "pushbutton"}])],
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(['such app', 'wow', {'string': 'headingLevel', 'args': [1]}])],
+ [ContentMessages.focusSelector('button#home', false),
+ new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])],
+
+ // Blur button and reset cursor
+ [ContentMessages.focusSelector('button#home', true), null],
+ [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
+
+ // XXX: Set focus on iframe itself.
+ // XXX: Set focus on element in iframe when cursor is outside of it.
+ // XXX: Set focus on element in iframe when cursor is in iframe.
+
+ // aria-hidden element that the virtual cursor is positioned on
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])],
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(["Back", {"string": "pushbutton"}])],
+ [doc.defaultView.ariaHideBack,
+ new ExpectedCursorChange(
+ ["such app", "wow", {"string": "headingLevel","args": [1]}])],
+ // Changing aria-hidden attribute twice and making sure that the event
+ // is fired only once when the actual change happens.
+ [doc.defaultView.ariaHideBack],
+ [doc.defaultView.ariaShowBack],
+ [ContentMessages.simpleMovePrevious,
+ new ExpectedCursorChange(["Back", {"string": "pushbutton"}])],
+ [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
+
+ // aria-hidden on the iframe that has the vc.
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])],
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(["Back", {"string": "pushbutton"}])],
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(['such app', 'wow', {'string': 'headingLevel', 'args': [1]}])],
+ [doc.defaultView.ariaHideIframe,
+ new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])],
+ [doc.defaultView.ariaShowIframe],
+ [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
+
+ // aria-hidden element and auto Move
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])],
+ [doc.defaultView.ariaHideBack],
+ [ContentMessages.focusSelector('button#back', false),
+ // Must not speak Back button as it is aria-hidden
+ new ExpectedCursorChange(
+ ["such app", "wow", {"string": "headingLevel","args": [1]}])],
+ [doc.defaultView.ariaShowBack],
+ [ContentMessages.focusSelector('button#back', true), null],
+ [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
+
+ // Open dialog in outer doc, while cursor is also in outer doc
+ [ContentMessages.simpleMoveLast,
+ new ExpectedCursorChange(['Traversal Rule test document', 'mover',
+ 'medium', {'string': 'slider'}])],
+ [doc.defaultView.showAlert,
+ new ExpectedCursorChange(['This is an alert!',
+ {'string': 'headingLevel', 'args': [1]},
+ {'string': 'dialog'}])],
+
+ [doc.defaultView.hideAlert,
+ new ExpectedCursorChange(['Traversal Rule test document', 'mover',
+ 'medium', {'string': 'slider'}])],
+
+ [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
+
+ // Open dialog in outer doc, while cursor is in inner frame
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])],
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(["Back", {"string": "pushbutton"}])],
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(
+ ['such app', 'wow', {'string': 'headingLevel', 'args': [1]}])],
+ [doc.defaultView.showAlert, new ExpectedCursorChange(['This is an alert!',
+ {'string': 'headingLevel', 'args': [1]},
+ {'string': 'dialog'}])],
+
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(['Do you agree?'])],
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(['Yes', {'string': 'pushbutton'}])],
+ [ContentMessages.activateCurrent(),
+ new ExpectedClickAction(),
+ new ExpectedCursorChange(
+ ['such app', 'wow', {'string': 'headingLevel', 'args': [1]}])],
+
+ [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
+
+ // Open dialog, then focus on something when closing
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])],
+ [doc.defaultView.showAlert,
+ new ExpectedCursorChange(['This is an alert!',
+ {'string': 'headingLevel', 'args': [1]}, {'string': 'dialog'}])],
+
+ [function hideAlertAndFocusHomeButton() {
+ doc.defaultView.hideAlert();
+ doc.querySelector('button#home').focus();
+ }, new ExpectedCursorChange(['Traversal Rule test document',
+ 'Home', {'string': 'pushbutton'}])],
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(['banana', {'string': 'pushbutton'}])]
+ [ContentMessages.simpleMoveNext, new ExpectedNoMove()]
+ ]);
+
+ addA11yLoadEvent(function() {
+ contentTest.start(function () {
+ closeBrowserWindow();
+ SimpleTest.finish();
+ });
+ }, doc.defaultView)
+ });
+ iframe.src = 'data:text/html;charset=utf-8,' + doc.defaultView.frameContents;
+ doc.getElementById('appframe').appendChild(iframe);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(
+ function () {
+ openBrowserWindow(
+ function () {
+ SpecialPowers.pushPrefEnv({
+ 'set': [
+ // TODO: remove this as part of bug 820712
+ ['network.disable.ipc.security', true],
+
+
+ ['dom.ipc.browser_frames.oop_by_default', true],
+ ['dom.mozBrowserFramesEnabled', true],
+ ['browser.pagethumbnails.capturing_disabled', true]
+ ]
+ }, doTest) },
+ getRootDirectory(window.location.href) + 'doc_content_integration.html');
+ });
+ </script>
+</head>
+<body id="body">
+
+ <a target="_blank"
+ title="Add tests for OOP message handling and general integration"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=972047">Mozilla Bug 933808</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/jsat/test_content_text.html b/accessible/tests/mochitest/jsat/test_content_text.html
new file mode 100644
index 000000000..558b819e9
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/test_content_text.html
@@ -0,0 +1,292 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Tests AccessFu content integration</title>
+ <meta charset="utf-8" />
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js">
+ </script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/chrome-harness.js">
+ </script>
+
+ <script type="application/javascript" src="../common.js"></script>
+ <script type="application/javascript" src="../browser.js"></script>
+ <script type="application/javascript" src="../events.js"></script>
+ <script type="application/javascript" src="../role.js"></script>
+ <script type="application/javascript" src="../states.js"></script>
+ <script type="application/javascript" src="../layout.js"></script>
+ <script type="application/javascript" src="jsatcommon.js"></script>
+
+ <script type="application/javascript">
+ function doTest() {
+ var doc = currentTabDocument();
+ var textTest = new AccessFuContentTest(
+ [
+ // Read-only text tests
+ [ContentMessages.simpleMoveFirst,
+ new ExpectedCursorChange(
+ ['Text content test document', 'These are my awards, Mother. ' +
+ 'From Army. The seal is for marksmanship, and the gorilla is ' +
+ 'for sand racing.'])],
+ [ContentMessages.moveNextBy('word'),
+ new ExpectedCursorTextChange('These', 0, 5)],
+ [ContentMessages.moveNextBy('word'),
+ new ExpectedCursorTextChange('are', 6, 9)],
+ [ContentMessages.moveNextBy('word'),
+ new ExpectedCursorTextChange('my', 10, 12)],
+ [ContentMessages.moveNextBy('word'),
+ new ExpectedCursorTextChange('awards,', 13, 20)],
+ [ContentMessages.moveNextBy('word'),
+ new ExpectedCursorTextChange('Mother.', 21, 28)],
+ [ContentMessages.movePreviousBy('word'),
+ new ExpectedCursorTextChange('awards,', 13, 20)],
+ [ContentMessages.movePreviousBy('word'),
+ new ExpectedCursorTextChange('my', 10, 12)],
+ [ContentMessages.movePreviousBy('word'),
+ new ExpectedCursorTextChange('are', 6, 9)],
+ [ContentMessages.movePreviousBy('word'),
+ new ExpectedCursorTextChange('These', 0, 5)],
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(['You\'re a good guy, mon frere. ' +
+ 'That means brother in French. ' +
+ 'I don\'t know how I know that. ' +
+ 'I took four years of Spanish.'])],
+ // XXX: Word boundary should be past the apostraphe.
+ [ContentMessages.moveNextBy('word'),
+ new ExpectedCursorTextChange('You\'re', 0, 6,
+ { android_todo: true /* Bug 980512 */})],
+
+ // Editable text tests.
+ [ContentMessages.focusSelector('textarea'),
+ new ExpectedAnnouncement('editing'),
+ new ExpectedEditState({
+ editing: true,
+ multiline: true,
+ atStart: true,
+ atEnd: false
+ }),
+ new ExpectedCursorChange(
+ ['Please refrain from Mayoneggs during this salmonella scare.',
+ {string: 'textarea'}]),
+ new ExpectedTextSelectionChanged(0, 0)
+ ],
+ [ContentMessages.activateCurrent(10),
+ new ExpectedTextCaretChanged(0, 10),
+ new ExpectedEditState({ editing: true,
+ multiline: true,
+ atStart: false,
+ atEnd: false }),
+ new ExpectedTextSelectionChanged(10, 10)],
+ [ContentMessages.activateCurrent(20),
+ new ExpectedTextCaretChanged(10, 20),
+ new ExpectedTextSelectionChanged(20, 20)
+ ],
+ [ContentMessages.moveCaretNextBy('word'),
+ new ExpectedTextCaretChanged(20, 29),
+ new ExpectedTextSelectionChanged(29, 29)
+ ],
+ [ContentMessages.moveCaretNextBy('word'),
+ new ExpectedTextCaretChanged(29, 36),
+ new ExpectedTextSelectionChanged(36, 36)
+ ],
+ [ContentMessages.moveCaretNextBy('character'),
+ new ExpectedTextCaretChanged(36, 37),
+ new ExpectedTextSelectionChanged(37, 37)
+ ],
+ [ContentMessages.moveCaretNextBy('character'),
+ new ExpectedTextCaretChanged(37, 38),
+ new ExpectedTextSelectionChanged(38, 38)
+ ],
+ [ContentMessages.moveCaretNextBy('paragraph'),
+ new ExpectedTextCaretChanged(38, 59),
+ new ExpectedTextSelectionChanged(59, 59)
+ ],
+ [ContentMessages.moveCaretPreviousBy('word'),
+ new ExpectedTextCaretChanged(53, 59),
+ new ExpectedTextSelectionChanged(53, 53)
+ ],
+
+ // bug xxx
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(
+ ['So we don\'t get dessert?', {string: 'label'}],
+ { focused: 'html'}),
+ new ExpectedAnnouncement('navigating'),
+ new ExpectedEditState({
+ editing: false,
+ multiline: false,
+ atStart: true,
+ atEnd: false })],
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(
+ [{ string : 'entry' }],
+ { focused: 'html'})],
+ [ContentMessages.activateCurrent(0),
+ new ExpectedClickAction(),
+ new ExpectedAnnouncement('editing'),
+ new ExpectedEditState({
+ editing: true,
+ multiline: false,
+ atStart: true,
+ atEnd: true
+ }, { focused: 'input[type=text]' }),
+ new ExpectedTextSelectionChanged(0, 0),
+ new ExpectedTextSelectionChanged(0, 0)
+ ],
+ [ContentMessages.simpleMovePrevious,
+ new ExpectedCursorChange(
+ ['So we don\'t get dessert?', {string: 'label'}]),
+ new ExpectedAnnouncement('navigating'),
+ new ExpectedEditState({
+ editing: false,
+ multiline: false,
+ atStart: true,
+ atEnd: false
+ },{ focused: 'html' })
+ ],
+ [ContentMessages.simpleMoveNext,
+ new ExpectedCursorChange(
+ [{ string : 'entry' }],
+ { focused: 'html'})],
+ [ContentMessages.activateCurrent(0),
+ new ExpectedClickAction(),
+ new ExpectedAnnouncement('editing'),
+ new ExpectedEditState({
+ editing: true,
+ multiline: false,
+ atStart: true,
+ atEnd: true
+ },
+ { focused: 'input[type=text]' }),
+ new ExpectedTextSelectionChanged(0, 0)],
+ [ContentMessages.simpleMovePrevious,
+ new ExpectedCursorChange(
+ [ 'So we don\'t get dessert?', {string: 'label'} ]),
+ new ExpectedAnnouncement('navigating'),
+ new ExpectedEditState({
+ editing: false,
+ multiline: false,
+ atStart: true,
+ atEnd: false
+ }, { focused: 'html' })],
+
+ [ContentMessages.focusSelector('input'),
+ new ExpectedAnnouncement('editing'),
+ new ExpectedEditState({
+ editing: true,
+ multiline: false,
+ atStart: true,
+ atEnd: true
+ }),
+ new ExpectedCursorChange([{string: 'entry'}]),
+ new ExpectedTextSelectionChanged(0, 0)
+ ],
+ [function() {
+ SpecialPowers.pushPrefEnv({"set": [[KEYBOARD_ECHO_SETTING, 3]]}, typeKey('a')());
+ },
+ new ExpectedTextChanged('a'),
+ new ExpectedTextSelectionChanged(1, 1),
+ ],
+ [typeKey('b'),
+ new ExpectedTextChanged('b'),
+ new ExpectedTextSelectionChanged(2, 2),
+ ],
+ [typeKey('c'),
+ new ExpectedTextChanged('c'),
+ new ExpectedTextSelectionChanged(3, 3),
+ ],
+ [typeKey('d'),
+ new ExpectedTextChanged('d'),
+ new ExpectedTextSelectionChanged(4, 4),
+ ],
+ [typeKey(' '),
+ new ExpectedTextChanged(' abcd'),
+ new ExpectedTextSelectionChanged(5, 5),
+ ],
+ [typeKey('e'),
+ new ExpectedTextChanged('e'),
+ new ExpectedTextSelectionChanged(6, 6),
+ ],
+ [function() {
+ SpecialPowers.pushPrefEnv({"set": [[KEYBOARD_ECHO_SETTING, 2]]}, typeKey('a')());
+ },
+ new ExpectedTextChanged(''),
+ new ExpectedTextSelectionChanged(7, 7),
+ ],
+ [typeKey('d'),
+ new ExpectedTextChanged(''),
+ new ExpectedTextSelectionChanged(8, 8),
+ ],
+ [typeKey(' '),
+ new ExpectedTextChanged(' ead'),
+ new ExpectedTextSelectionChanged(9, 9),
+ ],
+ [function() {
+ SpecialPowers.pushPrefEnv({"set": [[KEYBOARD_ECHO_SETTING, 1]]}, typeKey('f')());
+ },
+ new ExpectedTextChanged('f'),
+ new ExpectedTextSelectionChanged(10, 10),
+ ],
+ [typeKey('g'),
+ new ExpectedTextChanged('g'),
+ new ExpectedTextSelectionChanged(11, 11),
+ ],
+ [typeKey(' '),
+ new ExpectedTextChanged(' '),
+ new ExpectedTextSelectionChanged(12, 12),
+ ],
+ [function() {
+ SpecialPowers.pushPrefEnv({"set": [[KEYBOARD_ECHO_SETTING, 0]]}, typeKey('f')());
+ },
+ new ExpectedTextChanged(''),
+ new ExpectedTextSelectionChanged(13, 13),
+ ],
+ [typeKey('g'),
+ new ExpectedTextChanged(''),
+ new ExpectedTextSelectionChanged(14, 14),
+ ],
+ [typeKey(' '),
+ new ExpectedTextChanged(''),
+ new ExpectedTextSelectionChanged(15, 15),
+ ],
+ ]);
+
+ const KEYBOARD_ECHO_SETTING = 'accessibility.accessfu.keyboard_echo';
+ function typeKey(key) {
+ return function() { synthesizeKey(key, {}, currentTabWindow()); };
+ }
+
+ addA11yLoadEvent(function() {
+ textTest.start(function () {
+ closeBrowserWindow();
+ SimpleTest.finish();
+ });
+ }, doc.defaultView);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(
+ function () {
+ openBrowserWindow(
+ doTest,
+ getRootDirectory(window.location.href) + "doc_content_text.html");
+ });
+ </script>
+</head>
+<body id="body">
+
+ <a target="_blank"
+ title="Add tests for text editing and navigating"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=972047">Mozilla Bug 933808</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/jsat/test_explicit_names.html b/accessible/tests/mochitest/jsat/test_explicit_names.html
new file mode 100644
index 000000000..fb7ed2022
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/test_explicit_names.html
@@ -0,0 +1,191 @@
+<html>
+<head>
+ <title>[AccessFu] Trust explicitly associated names when speaking certain elements</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="output.js"></script>
+ <script type="application/javascript">
+
+ function doTest() {
+ // Test the following accOrElmOrID.
+ var tests = [{
+ accOrElmOrID: "anchor1",
+ expected: [{"string": "link"}, "title"]
+ }, {
+ accOrElmOrID: "anchor2",
+ expected: [{"string": "link"}, "This is a link"]
+ }, {
+ accOrElmOrID: "button1",
+ expected: [{"string": "pushbutton"}, "Press me"]
+ }, {
+ accOrElmOrID: "button2",
+ expected: [{"string": "pushbutton"}, "Press me"]
+ }, {
+ accOrElmOrID: "textarea1",
+ expected: [{"string": "textarea"}, "This is the text area text.",
+ "Test Text Area"]
+ }, {
+ accOrElmOrID: "textarea2",
+ expected: [{"string": "textarea"}, "This is the text area text."]
+ }, {
+ accOrElmOrID: "heading1",
+ expected: [{"string": "headingLevel", "args": [1]}, "Test heading",
+ "This is the heading."]
+ }, {
+ accOrElmOrID: "heading1",
+ oldAccOrElmOrID: null,
+ expected: [null /* parent doc title */, document.title,
+ {"string": "headingLevel", "args": [1]}, "Test heading",
+ "This is the heading."]
+ }, {
+ accOrElmOrID: "heading2",
+ expected: [{"string": "headingLevel", "args": [1]},
+ "This is the heading."]
+ }, {
+ accOrElmOrID: "list",
+ expected: [{"string": "list"}, {"string": "listItemsCount", "count": 2},
+ "Test List", {"string": "listStart"}, "Top of the list",
+ {"string": "listEnd"}, "2.", "list two"]
+ }, {
+ accOrElmOrID: "dlist",
+ expected: [{"string": "definitionlist"},
+ {"string": "listItemsCount", "count": 0.5}, "Test Definition List",
+ "dd one"]
+ }, {
+ accOrElmOrID: "li_one",
+ expected: [{"string": "list"}, {"string": "listItemsCount", "count": 2},
+ "Test List", {"string": "listStart"}, "Top of the list"]
+ }, {
+ accOrElmOrID: "li_two",
+ expected: [{"string": "list"}, {"string": "listItemsCount", "count": 2},
+ "Test List", {"string": "listEnd"}, "2.", "list two"]
+ }, {
+ accOrElmOrID: "cell",
+ expected: [{"string": "table"},
+ {"string": "tblColumnInfo", "count": 1},
+ {"string": "tblRowInfo", "count": 1}, "Fruits and vegetables",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [1]}, "List of Fruits",
+ {"string": "list"}, {"string": "listItemsCount", "count": 4},
+ {"string": "listStart"}, {"string": "link"}, "Apples",
+ {"string": "link"}, "Bananas",
+ {"string": "link"}, "Peaches", {"string": "listEnd"},
+ {"string": "link"}, "Plums"]
+ }, {
+ accOrElmOrID: "app.net",
+ expected: [{"string": "list"}, {"string": "listItemsCount", "count": 2},
+ {"string": "listStart"}, {"string": "link"}, "star",
+ {"string": "listEnd"}, {"string": "link"}, "repost"]
+ }, {
+ // Test pivot to list from li_one.
+ accOrElmOrID: "list",
+ oldAccOrElmOrID: "li_one",
+ expected: [{"string": "list"}, {"string": "listItemsCount", "count": 2},
+ "Test List", {"string": "listStart"}, "Top of the list",
+ {"string": "listEnd"}, "2.", "list two"]
+ }, {
+ // Test pivot to li_one from list.
+ accOrElmOrID: "li_one",
+ oldAccOrElmOrID: "list",
+ expected: [{"string": "listStart"}, "Top of the list"]
+ }, {
+ // Test pivot to "apples" link from the table cell.
+ accOrElmOrID: "apples",
+ oldAccOrElmOrID: "cell",
+ expected: [{"string": "list"}, {"string": "listItemsCount", "count": 4},
+ {"string": "listStart"}, {"string": "link"}, "Apples"]
+ }, {
+ // Test pivot to the table cell from the "apples" link.
+ accOrElmOrID: "cell",
+ oldAccOrElmOrID: "apples",
+ expected: ["List of Fruits", {"string": "list"},
+ {"string": "listItemsCount", "count": 4}, {"string": "listStart"},
+ {"string": "link"}, "Apples", {"string": "link"}, "Bananas",
+ {"string": "link"}, "Peaches", {"string": "listEnd"},
+ {"string": "link"}, "Plums"]
+ }];
+
+ SpecialPowers.pushPrefEnv({"set": [[PREF_UTTERANCE_ORDER, 0]]}, function() {
+ // Test various explicit names vs the utterance generated from subtrees.
+ tests.forEach(function run(test) {
+ testOutput(test.expected, test.accOrElmOrID, test.oldAccOrElmOrID, 1);
+ });
+ SimpleTest.finish();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+ <div id="root">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=845870"
+ title="[AccessFu] Trust explicitly associated names when speaking certain elements">
+ Mozilla Bug 845870
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ <button id="button1" aria-label="Press me">This is not a name</button>
+ <button id="button2">
+ Press me
+ </button>
+ <a id="anchor1" href="#test" title="title"></a>
+ <a id="anchor2" href="#test">This is a link</a>
+ <textarea id="textarea1" title="Test Text Area" cols="80" rows="5">This is the text area text.</textarea>
+ <textarea id="textarea2" cols="80" rows="5">
+ This is the text area text.
+ </textarea>
+ <h1 id="heading1" title="Test heading">This is the heading.</h1>
+ <h1 id="heading2">
+ This is the heading.
+ </h1>
+ <ol id="list" title="Test List">
+ <li id="li_one" aria-label="Top of the list">list one</li>
+ <li id="li_two">list two</li>
+ </ol>
+ <dl id="dlist" title="Test Definition List">
+ <dd id="dd_one">dd one</dd>
+ </dl>
+ <table>
+ <caption>Fruits and vegetables</caption>
+ <tr>
+ <td id="cell" aria-label="List of Fruits">
+ <ul style="list-style-type: none;">
+ <li><a id="apples" href="#">Apples</a></li>
+ <li><a id="bananas" href="#">Bananas</a></li>
+ <li><a href="#">Peaches</a></li>
+ <li>
+ <a href="#">
+
+ Plums
+ </a>
+ </li>
+ </ul>
+ </td>
+ </tr>
+ </table>
+ <!-- app.net -->
+ <ul id="app.net" class="unstyled ul-horizontal yui3-u fixed-right ta-right" style="list-style-type: none;">
+ <li class="yui3-u">
+ <a href="#star" data-starred="0" data-star-button="1" data-post-id="5098826" aria-label="star">
+ Garbage
+ </a>
+ </li>
+ <li class="yui3-u repost">
+ <a href="#repost" title="repost" data-repost-button="1" data-reposted="0" data-post-id="5098826">
+ <i aria-label="repost" class="icon-repost"></i>
+ </a>
+ </li>
+ </ul>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/jsat/test_gesture_tracker.html b/accessible/tests/mochitest/jsat/test_gesture_tracker.html
new file mode 100644
index 000000000..af2755455
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/test_gesture_tracker.html
@@ -0,0 +1,51 @@
+<html>
+
+<head>
+ <title>AccessFu tests for gesture tracker.</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="../common.js"></script>
+ <script type="application/javascript" src="../layout.js"></script>
+ <script type="application/javascript" src="./jsatcommon.js"></script>
+ <script type="application/javascript" src="./dom_helper.js"></script>
+ <script type="application/javascript">
+
+ function startGestureTracker() {
+ GestureTracker.reset();
+ AccessFuTest.nextTest();
+ }
+
+ function stopGestureTracker() {
+ GestureTracker.reset();
+ AccessFuTest.finish();
+ }
+
+ function doTest() {
+ loadJSON("./gestures.json", function onSuccess(gestures) {
+ AccessFuTest.addFunc(startGestureTracker);
+ AccessFuTest.sequenceCleanup = GestureTracker.reset.bind(
+ GestureTracker);
+ gestures.forEach(AccessFuTest.addSequence);
+ AccessFuTest.addFunc(stopGestureTracker);
+ AccessFuTest.waitForExplicitFinish();
+ Logger.logLevel = Logger.GESTURE;
+ AccessFuTest.runTests();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=981015"
+ title="AccessFu tests for gesture tracker.">
+ Mozilla Bug 981015
+ </a>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/jsat/test_hints.html b/accessible/tests/mochitest/jsat/test_hints.html
new file mode 100644
index 000000000..d5691b97a
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/test_hints.html
@@ -0,0 +1,89 @@
+<html>
+<head>
+ <title> [AccessFu] Interaction Hints </title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="output.js"></script>
+ <script type="application/javascript">
+
+ function doTest() {
+ var tests = [{
+ accOrElmOrID: 'can_wheel',
+ expectedHints: ['Swipe with two fingers to move between pages']
+ }, {
+ accOrElmOrID: 'nested_link',
+ expectedHints: [{string: 'link-hint'},
+ 'Swipe with two fingers to move between pages']
+ }, {
+ accOrElmOrID: 'nested_link',
+ oldAccOrElmOrID: 'can_wheel',
+ expectedHints: [{string: 'link-hint'}]
+ }, {
+ accOrElmOrID: 'link_with_default_hint',
+ expectedHints: [{string: 'link-hint'}]
+ }, {
+ accOrElmOrID: 'link_with_hint_override',
+ expectedHints: ['Tap and hold to get to menu']
+ }, {
+ accOrElmOrID: 'button_with_default_hint',
+ expectedHints: [{string: 'pushbutton-hint'}]
+ }, {
+ accOrElmOrID: 'button_with_hint_override',
+ expectedHints: ['Tap and hold to activate']
+ }, {
+ accOrElmOrID: 'nested_link2',
+ expectedHints: [{string: 'link-hint'}]
+ }, {
+ accOrElmOrID: 'nested_link3',
+ expectedHints: [{string: 'link-hint'}, {string: 'pushbutton-hint'},
+ "Double tap and hold to activate"]
+ }, {
+ accOrElmOrID: 'menuitemradio',
+ expectedHints: [{string: 'radiomenuitem-hint'}]
+ }];
+
+ // Test hints.
+ tests.forEach(function run(test) {
+ testHints(test.expectedHints, test.accOrElmOrID, test.oldAccOrElmOrID);
+ });
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+ <div id="root">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1069574"
+ title="[AccessFu] Interaction Hints">
+ Mozilla Bug 1069574
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ <span role="region" id="can_wheel" aria-moz-hint="Swipe with two fingers to move between pages">
+ <a href="#" id="nested_link">I can be clicked</a>
+ </span>
+ <span role="region" aria-moz-hint="">
+ <a><a href="#" id="nested_link2">I can be clicked</a></a>
+ </span>
+ <span role="region" aria-moz-hint="Double tap and hold to activate">
+ <button><a href="#" id="nested_link3">I can be clicked</a></button>
+ </span>
+ <a href="#" id="link_with_default_hint">I can be clicked</a>
+ <a href="#" id="link_with_hint_override" aria-moz-hint="Tap and hold to get to menu">I am a special link</a>
+ <button id="button_with_default_hint">Toggle</button>
+ <button id="button_with_hint_override" aria-moz-hint="Tap and hold to activate">Special</button>
+ <span id="menuitemradio" role="menuitemradio">Item 1</span>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/jsat/test_landmarks.html b/accessible/tests/mochitest/jsat/test_landmarks.html
new file mode 100644
index 000000000..8b1a16f83
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/test_landmarks.html
@@ -0,0 +1,183 @@
+<html>
+<head>
+ <title> [AccessFu] Speak landmarks</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="output.js"></script>
+ <script type="application/javascript"
+ src="jsatcommon.js"></script>
+ <script type="application/javascript">
+
+ function doTest() {
+ // Test the following accOrElmOrID.
+ var tests = [{
+ accOrElmOrID: "nav",
+ expectedUtterance: [[{"string": "navigation"}, "a nav"],
+ ["a nav", {"string": "navigation"}]],
+ expectedBraille: [[{"string": "navigation"}, "a nav"],
+ ["a nav", {"string": "navigation"}]]
+ }, {
+ accOrElmOrID: "main",
+ expectedUtterance: [[{"string": "main"}, "a main area"],
+ ["a main area", {"string": "main"}]],
+ expectedBraille: [[{"string": "main"}, "a main area"],
+ ["a main area", {"string": "main"}]]
+ }, {
+ accOrElmOrID: "header",
+ expectedUtterance: [
+ [{"string": "banner"}, {"string": "header"}, "a header"],
+ ["a header", {"string": "header"}, {"string": "banner"}]],
+ expectedBraille: [
+ [{"string": "banner"}, {"string": "headerAbbr"}, "a header"],
+ ["a header", {"string": "headerAbbr"}, {"string": "banner"}]]
+ }, {
+ accOrElmOrID: "footer",
+ expectedUtterance: [
+ [{"string": "contentinfo"}, {"string": "footer"}, "a footer"],
+ ["a footer", {"string": "footer"}, {"string": "contentinfo"}]],
+ expectedBraille: [
+ [{"string": "contentinfo"}, {"string": "footerAbbr"}, "a footer"],
+ ["a footer", {"string": "footerAbbr"}, {"string": "contentinfo"}]]
+ }, {
+ accOrElmOrID: "article_header",
+ expectedUtterance: [
+ [{"string": "header"}, "a header within an article"],
+ ["a header within an article", {"string": "header"}]],
+ expectedBraille: [
+ [{"string": "headerAbbr"}, "a header within an article"],
+ ["a header within an article", {"string": "headerAbbr"}]],
+ }, {
+ accOrElmOrID: "article_footer",
+ expectedUtterance: [
+ [{"string": "footer"}, "a footer within an article"],
+ ["a footer within an article", {"string": "footer"}]],
+ expectedBraille: [
+ [{"string": "footerAbbr"}, "a footer within an article"],
+ ["a footer within an article", {"string": "footerAbbr"}]]
+ }, {
+ accOrElmOrID: "section_header",
+ expectedUtterance: [[{"string":"header"}, "a header within a section"],
+ ["a header within a section", {"string":"header"}]],
+ expectedBraille: [
+ [{"string":"headerAbbr"}, "a header within a section"],
+ ["a header within a section", {"string":"headerAbbr"}]]
+ }, {
+ accOrElmOrID: "section_footer",
+ expectedUtterance: [
+ [{"string": "footer"}, "a footer within a section"],
+ ["a footer within a section", {"string": "footer"}]],
+ expectedBraille: [
+ [{"string": "footerAbbr"}, "a footer within a section"],
+ ["a footer within a section", {"string": "footerAbbr"}]]
+ }, {
+ accOrElmOrID: "aside",
+ expectedUtterance: [
+ [{"string": "complementary"}, "by the way I am an aside"],
+ ["by the way I am an aside", {"string": "complementary"}]],
+ expectedBraille: [
+ [{"string": "complementary"}, "by the way I am an aside"],
+ ["by the way I am an aside", {"string": "complementary"}]]
+ }, {
+ accOrElmOrID: "main_element",
+ expectedUtterance: [[{"string": "main"}, "another main area"],
+ ["another main area", {"string": "main"}]],
+ expectedBraille: [[{"string": "main"}, "another main area"],
+ ["another main area", {"string": "main"}]]
+ }, {
+ accOrElmOrID: "complementary",
+ expectedUtterance: [[{"string": "list"},
+ {"string": "listItemsCount", "count": 1}, {"string": "complementary"},
+ {"string": "listStart"}, "A complementary"], ["A complementary",
+ {"string": "listStart"}, {"string": "complementary"},
+ {"string": "list"}, {"string": "listItemsCount", "count": 1}]],
+ expectedBraille: [["*", {"string": "complementary"}, "A complementary"],
+ ["*", "A complementary", {"string": "complementary"}]]
+ }, {
+ accOrElmOrID: "parent_main",
+ expectedUtterance: [[{"string": "main"}, "a parent main",
+ {"string": "complementary"}, "a child complementary"],
+ ["a parent main", "a child complementary",
+ {"string": "complementary"}, {"string": "main"}]],
+ expectedBraille: [[{"string": "main"}, "a parent main",
+ {"string": "complementary"}, "a child complementary"],
+ ["a parent main", "a child complementary",
+ {"string": "complementary"}, {"string": "main"}]]
+ }, {
+ accOrElmOrID: "child_complementary",
+ expectedUtterance: [[{"string": "main"}, {"string": "complementary"},
+ "a child complementary"], ["a child complementary",
+ {"string": "complementary"}, {"string": "main"}]],
+ expectedBraille: [[{"string": "complementary"},
+ "a child complementary"], ["a child complementary",
+ {"string": "complementary"}]]
+ }];
+
+ // Test outputs (utterance and braille) for landmarks.
+ function testOutputOrder(aOutputOrder) {
+ return function() {
+ SpecialPowers.pushPrefEnv({
+ "set": [[PREF_UTTERANCE_ORDER, aOutputOrder]]
+ }, function() {
+ tests.forEach(function run(test) {
+ testOutput(test.expectedUtterance[aOutputOrder], test.accOrElmOrID,
+ test.oldAccOrElmOrID, 1);
+ testOutput(test.expectedBraille[aOutputOrder], test.accOrElmOrID,
+ test.oldAccOrElmOrID, 0);
+ });
+ AccessFuTest.nextTest();
+ });
+ };
+ }
+
+ AccessFuTest.addFunc(testOutputOrder(0));
+ AccessFuTest.addFunc(testOutputOrder(1));
+ AccessFuTest.waitForExplicitFinish();
+ AccessFuTest.runTests();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+ <div id="root">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=888256"
+ title="[AccessFu] Speak landmarks">
+ Mozilla Bug 888256
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ <nav id="nav">a nav</nav>
+ <header id="header">a header</header>
+ <footer id="footer">a footer</footer>
+ <article id="article_with_header_and_footer">
+ <header id="article_header">a header within an article</header>
+ <footer id="article_footer">a footer within an article</footer>
+ </article>
+ <section id="section_with_header_and_footer">
+ <header id="section_header">a header within a section</header>
+ <footer id="section_footer">a footer within a section</footer>
+ </section>
+ <aside id="aside">by the way I am an aside</aside>
+ <article id="main" role="main">a main area</article>
+ <main id="main_element">another main area</main>
+ <ul style="list-style-type: none;">
+ <li role="complementary" id="complementary">
+ A complementary
+ </li>
+ </ul>
+ <main id="parent_main">
+ a parent main
+ <p id="child_complementary" role="complementary">a child complementary</article>
+ </main>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/jsat/test_live_regions.html b/accessible/tests/mochitest/jsat/test_live_regions.html
new file mode 100644
index 000000000..53828f1b1
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/test_live_regions.html
@@ -0,0 +1,472 @@
+<html>
+
+<head>
+ <title>AccessFu tests for live regions support</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="./jsatcommon.js"></script>
+ <script type="application/javascript">
+
+ function startAccessFu() {
+ SpecialPowers.pushPrefEnv({"set": [['accessibility.accessfu.activate', 1]]});
+ AccessFuTest.once_log("EventManager.start", AccessFuTest.nextTest);
+ }
+
+ function stopAccessFu() {
+ SpecialPowers.pushPrefEnv({"set": [['accessibility.accessfu.activate', 0]]});
+ AccessFuTest.once_log("EventManager.stop", () => AccessFuTest.finish());
+ }
+
+ function hide(id) {
+ var element = document.getElementById(id);
+ element.style.display = "none";
+ }
+
+ function show(id) {
+ var element = document.getElementById(id);
+ element.style.display = "block";
+ }
+
+ function ariaHide(id) {
+ var element = document.getElementById(id);
+ element.setAttribute('aria-hidden', true);
+ }
+
+ function ariaShow(id) {
+ var element = document.getElementById(id);
+ element.setAttribute('aria-hidden', false);
+ }
+
+ function udpate(id, text, property) {
+ var element = document.getElementById(id);
+ element[property] = text;
+ }
+
+ function updateText(id, text) {
+ udpate(id, text, "textContent");
+ }
+
+ function updateHTML(id, text) {
+ udpate(id, text, "innerHTML");
+ }
+
+ var tests = [{
+ expected: {
+ "eventType": "liveregion-change",
+ "data": [{"string": "hidden"}, "I will be hidden"],
+ "options": {
+ "enqueue": true
+ }
+ },
+ action: function action() {
+ ["to_hide1", "to_hide2", "to_hide3", "to_hide4"].forEach(id => hide(id));
+ }
+ }, {
+ expected: {
+ "eventType": "liveregion-change",
+ "data": [{"string": "hidden"}, "I will be hidden"],
+ "options": {
+ "enqueue": true
+ }
+ },
+ action: function action() {
+ ["to_hide_descendant1", "to_hide_descendant2",
+ "to_hide_descendant3", "to_hide_descendant4"].forEach(id => hide(id));
+ }
+ }, {
+ expected: {
+ "eventType": "liveregion-change",
+ "data": ["I will be shown"],
+ "options": {
+ "enqueue": true
+ }
+ },
+ action: function action() {
+ ["to_show1", "to_show2", "to_show3", "to_show4"].forEach(id => show(id));
+ }
+ }, {
+ expected: {
+ "eventType": "liveregion-change",
+ "data": ["I will be shown"],
+ "options": {
+ "enqueue": true
+ }
+ },
+ action: function action() {
+ ["to_show_descendant1", "to_show_descendant2",
+ "to_show_descendant3", "to_show_descendant4"].forEach(id => show(id));
+ }
+ }, {
+ expected: {
+ "eventType": "liveregion-change",
+ "data": [{"string": "hidden"}, "I will be hidden"],
+ "options": {
+ "enqueue": true
+ }
+ },
+ action: function action() {
+ ["to_hide5", "to_hide6", "to_hide7", "to_hide8", "to_hide9"].forEach(id => ariaHide(id));
+ }
+ }, {
+ expected: {
+ "eventType": "liveregion-change",
+ "data": [{"string": "hidden"}, "I will be hidden"],
+ "options": {
+ "enqueue": true
+ }
+ },
+ action: function action() {
+ ["to_hide_descendant5", "to_hide_descendant6",
+ "to_hide_descendant7", "to_hide_descendant8"].forEach(id => ariaHide(id));
+ }
+ }, {
+ expected: {
+ "eventType": "liveregion-change",
+ "data": ["I will be shown"],
+ "options": {
+ "enqueue": true
+ }
+ },
+ action: function action() {
+ ["to_show5", "to_show6", "to_show7", "to_show8", "to_show9"].forEach(id => ariaShow(id));
+ }
+ }, {
+ expected: {
+ "eventType": "liveregion-change",
+ "data": ["I will be shown"],
+ "options": {
+ "enqueue": true
+ }
+ },
+ action: function action() {
+ ["to_show_descendant5", "to_show_descendant6",
+ "to_show_descendant7", "to_show_descendant8"].forEach(id => ariaShow(id));
+ }
+ }, {
+ expected: {
+ "eventType": "liveregion-change",
+ "data": [{"string": "hidden"}, "I will be hidden"],
+ "options": {
+ "enqueue": false
+ }
+ },
+ action: function action() {
+ hide("to_hide_live_assertive");
+ }
+ }, {
+ expected: {
+ "eventType": "liveregion-change",
+ "data": [{"string": "hidden"}, "I will be hidden"],
+ "options": {
+ "enqueue": false
+ }
+ },
+ action: function action() {
+ ariaHide("to_hide_live_assertive2");
+ }
+ }, {
+ expected: {
+ "eventType": "liveregion-change",
+ "data": ["I will be shown"],
+ "options": {
+ "enqueue": false
+ }
+ },
+ action: function action() {
+ ["to_show_live_off", "to_show_live_assertive"].forEach(id => show(id));
+ }
+ }, {
+ expected: {
+ "eventType": "liveregion-change",
+ "data": ["I will be shown"],
+ "options": {
+ "enqueue": false
+ }
+ },
+ action: function action() {
+ ["to_show_live_off2", "to_show_live_assertive2"].forEach(id => ariaShow(id));
+ }
+ }, {
+ expected: {
+ "eventType": "liveregion-change",
+ "data": ["Text Added"],
+ "options": {
+ "enqueue": false
+ }
+ },
+ action: function action() {
+ updateText("text_add", "Text Added");
+ }
+ }, {
+ expected: {
+ "eventType": "liveregion-change",
+ "data": ["Text Added"],
+ "options": {
+ "enqueue": false
+ }
+ },
+ action: function action() {
+ updateHTML("text_add", "Text Added");
+ }
+ }, {
+ expected: {
+ "eventType": "liveregion-change",
+ "data": [{"string": "hidden"}, "Text Removed"],
+ "options": {
+ "enqueue": true
+ }
+ },
+ action: function action() {
+ updateText("text_remove", "");
+ }
+ }, {
+ expected: {
+ "eventType": "liveregion-change",
+ "data": ["Descendant Text Added"],
+ "options": {
+ "enqueue": false
+ }
+ },
+ action: function action() {
+ updateText("text_add_descendant", "Descendant Text Added");
+ }
+ }, {
+ expected: {
+ "eventType": "liveregion-change",
+ "data": ["Descendant Text Added"],
+ "options": {
+ "enqueue": false
+ }
+ },
+ action: function action() {
+ updateHTML("text_add_descendant", "Descendant Text Added");
+ }
+ }, {
+ expected: {
+ "eventType": "liveregion-change",
+ "data": [{"string": "hidden"}, "Descendant Text Removed"],
+ "options": {
+ "enqueue": true
+ }
+ },
+ action: function action() {
+ updateText("text_remove_descendant", "");
+ }
+ }, {
+ expected: {
+ "eventType": "liveregion-change",
+ "data": ["Descendant Text Added"],
+ "options": {
+ "enqueue": false
+ }
+ },
+ action: function action() {
+ updateText("text_add_descendant2", "Descendant Text Added");
+ }
+ }, {
+ expected: {
+ "eventType": "liveregion-change",
+ "data": ["Descendant Text Added"],
+ "options": {
+ "enqueue": false
+ }
+ },
+ action: function action() {
+ updateHTML("text_add_descendant2", "Descendant Text Added");
+ }
+ }, {
+ expected: {
+ "eventType": "liveregion-change",
+ "data": [{"string": "hidden"}, "Descendant Text Removed"],
+ "options": {
+ "enqueue": true
+ }
+ },
+ action: function action() {
+ updateText("text_remove_descendant2", "");
+ }
+ }, {
+ expected: {
+ "eventType": "liveregion-change",
+ "data": ["I am replaced", {"string": "main"}],
+ "options": {
+ "enqueue": true
+ }
+ },
+ action: function action() {
+ var region = document.getElementById("to_replace_region");
+ var child = document.getElementById("to_replace");
+ child.setAttribute("role", "main");
+ }
+ }, {
+ expected: {
+ "eventType": "liveregion-change",
+ "data": ["I am a replaced text"],
+ "options": {
+ "enqueue": false
+ }
+ },
+ action: function action() {
+ updateText("to_replace_text", "I am a replaced text");
+ }
+ }, {
+ expected: {
+ "eventType": "liveregion-change",
+ "data": ["I am a replaced text"],
+ "options": {
+ "enqueue": false
+ }
+ },
+ action: function action() {
+ updateHTML("to_replace_text", "I am a replaced text");
+ }
+ }];
+
+ function doTest() {
+ AccessFuTest.addFunc(startAccessFu);
+ tests.forEach(function addTest(test) {
+ AccessFuTest.addFunc(function () {
+ AccessFuTest.once(test.expected, AccessFuTest.nextTest);
+ test.action();
+ });
+ });
+ AccessFuTest.addFunc(stopAccessFu);
+ AccessFuTest.waitForExplicitFinish();
+ AccessFuTest.runTests();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=795957"
+ title="[AccessFu] Support live regions">
+ Mozilla Bug 795957
+ </a>
+ <div id="root">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+
+ <p id="to_hide1">I should not be announced 1</p>
+ <p id="to_hide2" aria-live="polite">I should not be announced 2</p>
+ <p id="to_hide3" aria-live="assertive" aria-relevant="text">I should not be announced 3</p>
+ <p id="to_hide4" aria-live="polite" aria-relevant="all">I will be hidden</p>
+
+ <p id="to_hide5" aria-hidden="true">I should not be announced 5</p>
+ <p id="to_hide6">I should not be announced 6</p>
+ <p id="to_hide7" aria-live="polite">I should not be announced 7</p>
+ <p id="to_hide8" aria-live="assertive" aria-relevant="text">I should not be announced 8</p>
+ <p id="to_hide9" aria-live="polite" aria-relevant="all">I will be hidden</p>
+
+ <div>
+ <p id="to_hide_descendant1">I should not be announced 1</p>
+ </div>
+ <div aria-live="polite">
+ <p id="to_hide_descendant2">I should not be announced 2</p>
+ </div>
+ <div aria-live="assertive" aria-relevant="text">
+ <p id="to_hide_descendant3">I should not be announced 3</p>
+ </div>
+ <div aria-live="polite" aria-relevant="all">
+ <p id="to_hide_descendant4">I will be hidden</p>
+ </div>
+
+ <div>
+ <p id="to_hide_descendant5">I should not be announced 4</p>
+ </div>
+ <div aria-live="polite">
+ <p id="to_hide_descendant6">I should not be announced 5</p>
+ </div>
+ <div aria-live="assertive" aria-relevant="text">
+ <p id="to_hide_descendant7">I should not be announced 6</p>
+ </div>
+ <div aria-live="polite" aria-relevant="all">
+ <p id="to_hide_descendant8">I will be hidden</p>
+ </div>
+
+ <p id="to_show1" style="display: none">I should not be announced 1</p>
+ <p id="to_show2" aria-live="assertive" aria-relevant="text" style="display: none">I should not be announced 2</p>
+ <p id="to_show3" aria-live="polite" aria-relevant="removals" style="display: none">I should not be announced 3</p>
+ <p id="to_show4" aria-live="polite" aria-relevant="all" style="display: none">I will be shown</p>
+
+ <p id="to_show5" aria-hidden="false">I should not be announced 5</p>
+ <p id="to_show6" aria-hidden="true">I should not be announced 6</p>
+ <p id="to_show7" aria-hidden="true" aria-live="assertive" aria-relevant="text">I should not be announced 7</p>
+ <p id="to_show8" aria-hidden="true" aria-live="polite" aria-relevant="removals">I should not be announced 8</p>
+ <p id="to_show9" aria-hidden="true" aria-live="polite" aria-relevant="all">I will be shown</p>
+
+ <div>
+ <p id="to_show_descendant1" style="display: none">I should not be announced 1</p>
+ </div>
+ <div aria-live="polite" aria-relevant="removals">
+ <p id="to_show_descendant2" style="display: none">I should not be announced 2</p>
+ </div>
+ <div aria-live="assertive" aria-relevant="text">
+ <p id="to_show_descendant3" style="display: none">I should not be announced 3</p>
+ </div>
+ <div aria-live="polite" aria-relevant="all">
+ <p id="to_show_descendant4" style="display: none">I will be shown</p>
+ </div>
+
+ <div>
+ <p id="to_show_descendant5" aria-hidden="true">I should not be announced 5</p>
+ </div>
+ <div aria-live="polite" aria-relevant="removals">
+ <p id="to_show_descendant6" aria-hidden="true">I should not be announced 6</p>
+ </div>
+ <div aria-live="assertive" aria-relevant="text">
+ <p id="to_show_descendant7" aria-hidden="true">I should not be announced 7</p>
+ </div>
+ <div aria-live="polite" aria-relevant="all">
+ <p id="to_show_descendant8" aria-hidden="true">I will be shown</p>
+ </div>
+
+ <div aria-live="assertive" aria-relevant="all">
+ <p id="to_hide_live_assertive">I will be hidden</p>
+ </div>
+
+ <div aria-live="assertive" aria-relevant="all">
+ <p id="to_hide_live_assertive2">I will be hidden</p>
+ </div>
+ <p id="to_show_live_assertive" aria-live="assertive" style="display: none">I will be shown</p>
+
+ <p id="to_show_live_off" aria-live="off" style="display: none">I will not be shown</p>
+
+ <p id="to_show_live_assertive2" aria-live="assertive" aria-hidden="true">I will be shown</p>
+
+ <p id="to_show_live_off2" aria-live="off" aria-hidden="true">I will not be shown</p>
+
+ <div id="to_replace_region" aria-live="polite" aria-relevant="all">
+ <p id="to_replace">I am replaced</p>
+ </div>
+
+ <p id="to_replace_text" aria-live="assertive" aria-relevant="text">I am going to be replaced</p>
+
+ <p id="text_add" aria-live="assertive" aria-relevant="text"></p>
+ <p id="text_remove" aria-live="polite" aria-relevant="all">Text Removed</p>
+
+ <div aria-live="assertive" aria-relevant="all">
+ <p id="text_add_descendant"></p>
+ </div>
+ <div aria-live="polite" aria-relevant="text">
+ <p id="text_remove_descendant">Descendant Text Removed</p>
+ </div>
+ <div aria-live="assertive" aria-relevant="text">
+ <p id="text_add_descendant2"></p>
+ </div>
+ <div aria-live="polite" aria-relevant="all">
+ <p id="text_remove_descendant2">Descendant Text Removed</p>
+ </div>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/jsat/test_output.html b/accessible/tests/mochitest/jsat/test_output.html
new file mode 100644
index 000000000..ec2b289be
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/test_output.html
@@ -0,0 +1,673 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=753984
+-->
+ <head>
+ <title>[AccessFu] utterance order test</title>
+ <meta charset="utf-8">
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="./output.js"></script>
+ <script type="application/javascript"
+ src="./jsatcommon.js"></script>
+ <script type="application/javascript">
+
+ function doTest() {
+ // Test the following accOrElmOrID (with optional old accOrElmOrID).
+ // Note: each accOrElmOrID entry maps to a unique object utterance
+ // generator function within the UtteranceGenerator.
+ var tests = [{
+ accOrElmOrID: "anchor",
+ expectedUtterance: [[{"string": "link"}, "title"],
+ ["title", {"string": "link"}]],
+ expectedBraille: [[{"string": "linkAbbr"}, "title"],
+ ["title", {"string": "linkAbbr"}]]
+ }, {
+ accOrElmOrID: "anchor_titleandtext",
+ expectedUtterance: [[{"string": "link"}, "goes to the tests -",
+ "Tests"], ["Tests", "- goes to the tests", {"string": "link"}]],
+ expectedBraille: [[{"string": "linkAbbr"}, "goes to the tests -",
+ "Tests"], ["Tests", "- goes to the tests", {"string": "linkAbbr"}]],
+ }, {
+ accOrElmOrID: "anchor_duplicatedtitleandtext",
+ expectedUtterance: [[{"string": "link"}, "Tests"],
+ ["Tests", {"string": "link"}]],
+ expectedBraille: [[{"string": "linkAbbr"}, "Tests"],
+ ["Tests", {"string": "linkAbbr"}]]
+ }, {
+ accOrElmOrID: "anchor_arialabelandtext",
+ expectedUtterance: [[{"string": "link"}, "goes to the tests - Tests"],
+ ["Tests - goes to the tests", {"string": "link"}]],
+ expectedBraille: [[{"string": "linkAbbr"},
+ "goes to the tests - Tests"], ["Tests - goes to the tests",
+ {"string": "linkAbbr"}]],
+ }, {
+ accOrElmOrID: "textarea",
+ expectedUtterance: [[{"string": "textarea"},
+ "This is the text area text."], ["This is the text area text.",
+ {"string": "textarea"}]],
+ expectedBraille: [[{"string": "textareaAbbr"},
+ "This is the text area text."], ["This is the text area text.",
+ {"string": "textareaAbbr"}]],
+ }, {
+ accOrElmOrID: "heading",
+ expectedUtterance: [[{"string": "headingLevel", "args": [1]},
+ "Test heading"], ["Test heading",
+ {"string": "headingLevel", "args": [1]}]],
+ expectedBraille: [[{"string": "headingAbbr"}, "Test heading"],
+ ["Test heading", {"string": "headingAbbr"}]]
+ }, {
+ accOrElmOrID: "list",
+ expectedUtterance: [[{"string": "list"},
+ {"string": "listItemsCount", "count":1}, {"string": "listStart"},
+ "1.", "list one"], ["1.", "list one", {"string": "listStart"},
+ {"string": "list"}, {"string": "listItemsCount", "count":1}]
+ ],
+ expectedBraille: [[{"string": "listAbbr"}, "list one"],
+ ["list one", {"string": "listAbbr"}]]
+ }, {
+ accOrElmOrID: "dlist",
+ expectedUtterance: [[{"string": "definitionlist"},
+ {"string": "listItemsCount", "count": 0.5}, "dd one"], ["dd one",
+ {"string": "definitionlist"},
+ {"string": "listItemsCount", "count": 0.5}]
+ ],
+ expectedBraille: [[{"string": "definitionlistAbbr"}, "dd one"],
+ ["dd one", {"string": "definitionlistAbbr"}]]
+ }, {
+ accOrElmOrID: "li_one",
+ expectedUtterance: [[{"string": "list"},
+ {"string": "listItemsCount", "count": 1}, {"string": "listStart"},
+ "1.", "list one"], ["1.", "list one", {"string": "listStart"},
+ {"string": "list"}, {"string": "listItemsCount", "count": 1}]
+ ],
+ expectedBraille: [["1.", "list one"], ["1.", "list one"]]
+ },
+ {
+ accOrElmOrID: "li_two",
+ expectedUtterance: [[{"string": "list"},
+ {"string": "listItemsCount", "count": 1}, {"string": "listStart"},
+ "list two"], ["list two", {"string": "listStart"},
+ {"string": "list"}, {"string": "listItemsCount", "count": 1}]
+ ],
+ expectedBraille: [["*", "list two"], ["*", "list two"]]
+ }, {
+ accOrElmOrID: "cell",
+ expectedUtterance: [[{"string":"table"},
+ {"string": "tblColumnInfo", "count": 1},
+ {"string": "tblRowInfo", "count": 1}, "Fruits and vegetables",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [1]}, {"string": "list"},
+ {"string": "listItemsCount", "count": 4}, {"string": "listStart"},
+ {"string": "link"}, "Apples", {"string": "link"}, "Bananas",
+ {"string": "link"}, "Peaches", {"string": "listEnd"},
+ {"string": "link"}, "Plums"], ["Apples", {"string": "link"},
+ {"string": "listStart"}, "Bananas", {"string": "link"}, "Peaches",
+ {"string": "link"}, "Plums", {"string": "link"},
+ {"string": "listEnd"}, {"string": "list"},
+ {"string": "listItemsCount", "count": 4},
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [1]}, "Fruits and vegetables",
+ {"string":"table"}, {"string": "tblColumnInfo", "count": 1},
+ {"string": "tblRowInfo", "count": 1}]],
+ expectedBraille: [[{"string": "cellInfoAbbr", "args": [ 1, 1]},
+ {"string": "listAbbr"}, {"string": "linkAbbr"}, "Apples",
+ {"string": "linkAbbr"}, "Bananas", {"string": "linkAbbr"},
+ "Peaches", {"string": "linkAbbr"}, "Plums"], ["Apples",
+ {"string": "linkAbbr"}, "Bananas", {"string": "linkAbbr"},
+ "Peaches", {"string": "linkAbbr"}, "Plums", {"string": "linkAbbr"},
+ {"string": "listAbbr"},
+ {"string": "cellInfoAbbr", "args": [ 1, 1]}]]
+ }, {
+ accOrElmOrID: "date",
+ expectedUtterance: [[{"string": "textInputType_date"},
+ {"string": "entry"}, "2011-09-29"], ["2011-09-29",
+ {"string": "textInputType_date"}, {"string": "entry"}]],
+ expectedBraille: [[{"string": "textInputType_date"},
+ {"string": "entryAbbr"}, "2011-09-29"], ["2011-09-29",
+ {"string": "textInputType_date"}, {"string": "entryAbbr"}]]
+ }, {
+ accOrElmOrID: "email",
+ expectedUtterance: [[{"string": "textInputType_email"},
+ {"string": "entry"}, "test@example.com"], ["test@example.com",
+ {"string": "textInputType_email"}, {"string": "entry"}]],
+ expectedBraille: [[{"string": "textInputType_email"},
+ {"string": "entryAbbr"}, "test@example.com"], ["test@example.com",
+ {"string": "textInputType_email"}, {"string": "entryAbbr"}]]
+ }, {
+ accOrElmOrID: "search",
+ expectedUtterance: [[{"string": "textInputType_search"},
+ {"string": "entry"}, "This is a search"], ["This is a search",
+ {"string": "textInputType_search"}, {"string": "entry"}]],
+ expectedBraille: [[{"string": "textInputType_search"},
+ {"string": "entryAbbr"}, "This is a search"], ["This is a search",
+ {"string": "textInputType_search"}, {"string": "entryAbbr"}]]
+ }, {
+ accOrElmOrID: "tel",
+ expectedUtterance: [[{"string": "textInputType_tel"},
+ {"string": "entry"}, "555-5555"], ["555-5555",
+ {"string": "textInputType_tel"}, {"string": "entry"}]],
+ expectedBraille: [[{"string": "textInputType_tel"},
+ {"string": "entryAbbr"}, "555-5555"], ["555-5555",
+ {"string": "textInputType_tel"}, {"string": "entryAbbr"}]]
+ }, {
+ accOrElmOrID: "url",
+ expectedUtterance: [[{"string": "textInputType_url"},
+ {"string": "entry"}, "http://example.com"], ["http://example.com",
+ {"string": "textInputType_url"}, {"string": "entry"}]],
+ expectedBraille: [[{"string": "textInputType_url"},
+ {"string": "entryAbbr"}, "http://example.com"],
+ ["http://example.com", {"string": "textInputType_url"},
+ {"string": "entryAbbr"}]]
+ }, {
+ accOrElmOrID: "textInput",
+ expectedUtterance: [[{"string": "entry"}, "This is text."],
+ ["This is text.", {"string": "entry"}]],
+ expectedBraille: [[{"string": "entryAbbr"}, "This is text."],
+ ["This is text.", {"string": "entryAbbr"}]]
+ }, {
+ // Test pivot to list from li_one.
+ accOrElmOrID: "list",
+ oldAccOrElmOrID: "li_one",
+ expectedUtterance: [[{"string": "list"},
+ {"string": "listItemsCount", "count": 1}, {"string": "listStart"},
+ "1.", "list one"], ["1.", "list one", {"string": "listStart"},
+ {"string": "list"}, {"string": "listItemsCount", "count": 1}]
+ ],
+ expectedBraille: [[{"string": "listAbbr"}, "list one"],
+ ["list one", {"string": "listAbbr"}]]
+ }, {
+ // Test pivot to "apples" link from the table cell.
+ accOrElmOrID: "apples",
+ oldAccOrElmOrID: "cell",
+ expectedUtterance: [[{"string": "list"},
+ {"string": "listItemsCount", "count": 4}, {"string": "listStart"},
+ {"string": "link"}, "Apples"], ["Apples", {"string": "link"},
+ {"string": "listStart"}, {"string": "list"},
+ {"string": "listItemsCount", "count": 4}]
+ ],
+ expectedBraille: [["*", {"string": "linkAbbr"}, "Apples"],
+ ["*", "Apples", {"string": "linkAbbr"}]]
+ }, {
+ // Test pivot to "bananas" link from "apples" link.
+ accOrElmOrID: "bananas",
+ oldAccOrElmOrID: "apples",
+ expectedUtterance: [[{"string": "link"}, "Bananas"],
+ ["Bananas", {"string": "link"}]],
+ expectedBraille: [["*", {"string": "linkAbbr"}, "Bananas"],
+ ["*", "Bananas", {"string": "linkAbbr"}]]
+ }, {
+ // test unavailable state utterance
+ accOrElmOrID: "unavailableButton",
+ expectedUtterance: [[{"string": "stateUnavailable"},
+ {"string": "pushbutton"}, "I am unavailable"], ["I am unavailable",
+ {"string": "stateUnavailable"}, {"string": "pushbutton"}]],
+ expectedBraille: [[{"string": "pushbuttonAbbr"}, "I am unavailable"],
+ ["I am unavailable", {"string": "pushbuttonAbbr"}]]
+ }, {
+ // test expanded state utterance
+ accOrElmOrID: "expandedButton",
+ expectedUtterance: [[{"string": "stateExpanded"},
+ {"string": "pushbutton"}, "I am expanded"], ["I am expanded",
+ {"string": "stateExpanded"}, {"string": "pushbutton"}]],
+ expectedBraille: [[{"string": "pushbuttonAbbr"}, "I am expanded"],
+ ["I am expanded", {"string": "pushbuttonAbbr"}]]
+ }, {
+ // test collapsed state utterance
+ accOrElmOrID: "collapsedButton",
+ expectedUtterance: [[{"string": "stateCollapsed"},
+ {"string": "pushbutton"}, "I am collapsed"], ["I am collapsed",
+ {"string": "stateCollapsed"}, {"string": "pushbutton"}]],
+ expectedBraille: [[{"string": "pushbuttonAbbr"}, "I am collapsed"],
+ ["I am collapsed", {"string": "pushbuttonAbbr"}]]
+ }, {
+ // test required state utterance
+ accOrElmOrID: "requiredInput",
+ expectedUtterance: [[{"string": "stateRequired"}, {"string": "entry"},
+ "I am required"], ["I am required", {"string": "stateRequired"},
+ {"string": "entry"}]],
+ expectedBraille: [[{"string": "entryAbbr"}, "I am required"],
+ ["I am required", {"string": "entryAbbr"}]]
+ }, {
+ // test unavailable state utterance on inputs
+ accOrElmOrID: "readonlyInput",
+ expectedUtterance: [[{"string": "stateReadonly"}, {"string": "entry"},
+ "No edits"], ["No edits", {"string": "stateReadonly"},
+ {"string": "entry"}]],
+ expectedBraille: [[{"string": "entryAbbr"}, "No edits"],
+ ["No edits", {"string": "entryAbbr"}]]
+ }, {
+ // test unavailable state utterance on textareas
+ accOrElmOrID: "readonlyTextarea",
+ expectedUtterance: [[{"string": "stateReadonly"}, {"string": "textarea"},
+ "No editing"], ["No editing", {"string": "stateReadonly"},
+ {"string": "textarea"}]],
+ expectedBraille: [[{"string": "textareaAbbr"}, "No editing"],
+ ["No editing", {"string": "textareaAbbr"}]]
+ }, {
+ // test has popup state utterance
+ accOrElmOrID: "hasPopupButton",
+ expectedUtterance: [[{"string": "stateHasPopup"},
+ {"string": "buttonmenu"}, "I have a popup"], ["I have a popup",
+ {"string": "stateHasPopup"}, {"string": "buttonmenu"}]],
+ expectedBraille: [[{"string": "buttonmenuAbbr"}, "I have a popup"],
+ ["I have a popup", {"string": "buttonmenuAbbr"}]]
+ }, {
+ // Test selected tab
+ accOrElmOrID: "tab1",
+ expectedUtterance: [[{"string": "pagetablist"},
+ {"string": "stateSelected"}, {"string": "pagetab"},
+ {"string": "objItemOfN", "args": [1, 2]}, "Account"], ["Account",
+ {"string": "stateSelected"}, {"string": "pagetab"},
+ {"string": "objItemOfN", "args": [1, 2]}, {"string": "pagetablist"}]
+ ],
+ expectedBraille: [[{"string": "pagetabAbbr"},
+ {"string": "objItemOfN", "args": [1, 2]}, "Account"], ["Account",
+ {"string": "pagetabAbbr"},
+ {"string": "objItemOfN", "args": [1, 2]}]]
+ }, {
+ // Test unselected tab
+ accOrElmOrID: "tab2",
+ expectedUtterance: [[{"string": "pagetablist"}, {"string": "pagetab"},
+ {"string": "objItemOfN", "args": [2, 2]}, "Advanced"], ["Advanced",
+ {"string": "pagetab"}, {"string": "objItemOfN", "args": [2, 2]},
+ {"string": "pagetablist"}]],
+ expectedBraille: [[{"string": "pagetabAbbr"},
+ {"string": "objItemOfN", "args": [2, 2]}, "Advanced"], ["Advanced",
+ {"string": "pagetabAbbr"},
+ {"string": "objItemOfN", "args": [2, 2]}]]
+ }, {
+ // Landing on this label should mimic landing on the checkbox.
+ accOrElmOrID: "label1",
+ expectedUtterance: [[{"string": "stateNotChecked"},
+ {"string": "checkbutton"}, "Orange"], ["Orange",
+ {"string": "stateNotChecked"}, {"string": "checkbutton"}]],
+ expectedBraille: [[{"string": "stateUncheckedAbbr"}, "Orange"],
+ ["Orange", {"string": "stateUncheckedAbbr"}]]
+ }, {
+ // Here we get a top-level view of the form.
+ accOrElmOrID: "form1",
+ expectedUtterance: [[{"string": "label"},
+ {"string": "stateNotChecked"}, {"string": "checkbutton"}, "Orange",
+ "Orange", {"string": "stateNotChecked"}, {"string": "checkbutton"},
+ "Blue", {"string": "label"}, "Blue"], ["Orange",
+ {"string": "stateNotChecked"}, {"string": "checkbutton"}, "Orange",
+ {"string": "label"}, "Blue", {"string": "stateNotChecked"},
+ {"string": "checkbutton"}, "Blue", {"string": "label"}]],
+ expectedBraille: [[{"string": "labelAbbr"},
+ {"string": "stateUncheckedAbbr"}, "Orange", "Orange",
+ {"string": "stateUncheckedAbbr"}, "Blue", {"string": "labelAbbr"},
+ "Blue"], ["Orange", {"string": "stateUncheckedAbbr"}, "Orange",
+ {"string": "labelAbbr"}, "Blue", {"string": "stateUncheckedAbbr"},
+ "Blue", {"string": "labelAbbr"}]]
+ }, {
+ // This is a non-nesting label.
+ accOrElmOrID: "label2",
+ expectedUtterance: [[{"string": "label"}, "Blue"],
+ ["Blue", {"string": "label"}]],
+ expectedBraille: [[{"string": "labelAbbr"}, "Blue"],
+ ["Blue", {"string": "labelAbbr"}]]
+ }, {
+ // This is a distinct control.
+ accOrElmOrID: "input2",
+ expectedUtterance: [[{"string": "stateNotChecked"},
+ {"string": "checkbutton"}, "Blue"], ["Blue",
+ {"string": "stateNotChecked"}, {"string": "checkbutton"}]],
+ expectedBraille: [[{"string": "stateUncheckedAbbr"}, "Blue"], ["Blue",
+ {"string": "stateUncheckedAbbr"}]]
+ }, {
+ // This is a nested control.
+ accOrElmOrID: "input1",
+ expectedUtterance: [[{"string": "stateNotChecked"},
+ {"string": "checkbutton"}, "Orange"], ["Orange",
+ {"string": "stateNotChecked"}, {"string": "checkbutton"}]],
+ expectedBraille: [[{"string": "stateUncheckedAbbr"}, "Orange"],
+ ["Orange", {"string": "stateUncheckedAbbr"}]]
+ }, {
+ // Landing on this label should mimic landing on the entry.
+ accOrElmOrID: "label3",
+ expectedUtterance: [[{"string": "entry"}, "Joe", "First name:"],
+ ["First name:", "Joe", {"string": "entry"}]],
+ expectedBraille: [[{"string": "entryAbbr"}, "Joe", "First name:"],
+ ["First name:", "Joe", {"string": "entryAbbr"}]]
+ }, {
+ // This is a nested control with a value.
+ accOrElmOrID: "input3",
+ expectedUtterance: [[{"string": "entry"}, "Joe", "First name:"],
+ ["First name:", "Joe", {"string": "entry"}]],
+ expectedBraille: [[{"string": "entryAbbr"}, "Joe", "First name:"],
+ ["First name:", "Joe", {"string": "entryAbbr"}]]
+ }, {
+ // This is a nested control with a value.
+ accOrElmOrID: "input4",
+ expectedUtterance: [[{"string": "slider"}, "3", "Points:"],
+ ["Points:", "3", {"string": "slider"}]],
+ expectedBraille: [[{"string": "sliderAbbr"}, "3", "Points:"],
+ ["Points:", "3", {"string": "sliderAbbr"}]]
+ }, {
+ accOrElmOrID: "password",
+ expectedUtterance: [[{"string": "passwordtext"}, "Secret Password"],
+ ["Secret Password", {"string": "passwordtext"}]],
+ expectedBraille: [[{"string": "passwordtextAbbr"}, "Secret Password"],
+ ["Secret Password", {"string": "passwordtextAbbr"}]]
+ }, {
+ accOrElmOrID: "input5",
+ expectedUtterance: [[{"string": "stateChecked"},
+ {"string": "checkbutton"}, "Boring label"], ["Boring label",
+ {"string": "stateChecked"}, {"string": "checkbutton"}]],
+ expectedBraille: [[{"string": "stateCheckedAbbr"}, "Boring label"],
+ ["Boring label", {"string": "stateCheckedAbbr"}]]
+ }, {
+ accOrElmOrID: "radio_unselected",
+ expectedUtterance: [[{"string": "stateNotChecked"},
+ {"string": "radiobutton"}, "any old radio button"],
+ ["any old radio button", {"string": "stateNotChecked"},
+ {"string": "radiobutton"}]
+ ],
+ expectedBraille: [
+ [{"string": "stateUncheckedAbbr"}, "any old radio button"],
+ ["any old radio button", {"string": "stateUncheckedAbbr"}]]
+ }, {
+ accOrElmOrID: "radio_selected",
+ expectedUtterance: [[{"string": "stateChecked"},
+ {"string": "radiobutton"}, "a unique radio button"],
+ ["a unique radio button", {"string": "stateChecked"},
+ {"string": "radiobutton"}]],
+ expectedBraille: [
+ [{"string": "stateCheckedAbbr"}, "a unique radio button"],
+ ["a unique radio button", {"string": "stateCheckedAbbr"}]]
+ }, {
+ accOrElmOrID: "togglebutton_notpressed",
+ expectedUtterance: [[{"string": "togglebutton"}, "I am not pressed"],
+ ["I am not pressed", {"string": "togglebutton"}]],
+ expectedBraille: [
+ [{"string": "stateUnpressedAbbr"}, "I am not pressed"],
+ ["I am not pressed", {"string": "stateUnpressedAbbr"}]]
+ }, {
+ accOrElmOrID: "togglebutton_pressed",
+ expectedUtterance: [[{"string": "statePressed"},
+ {"string": "togglebutton"}, "I am pressed!"], ["I am pressed!",
+ {"string": "statePressed"}, {"string": "togglebutton"}]],
+ expectedBraille: [[{"string": "statePressedAbbr"}, "I am pressed!"],
+ ["I am pressed!", {"string": "statePressedAbbr"}]]
+ }, {
+ accOrElmOrID: "listbox-option",
+ expectedUtterance: [[{"string": "listbox"},
+ {"string": "listboxoption"}, "Search suggestion"],
+ ["Search suggestion", {"string": "listboxoption"},
+ {"string": "listbox"}]
+ ],
+ expectedBraille: [
+ [{"string": "listboxoptionAbbr"}, "Search suggestion"],
+ ["Search suggestion", {"string": "listboxoptionAbbr"}]]
+ }, {
+ accOrElmOrID: "listbox-option2",
+ oldAccOrElmOrID: "listbox-option",
+ expectedUtterance: [[{"string": "listboxoption"}, "555-12345"],
+ ["555-12345", {"string": "listboxoption"}]],
+ expectedBraille: [[{"string": "listboxoptionAbbr"}, "555-12345"],
+ ["555-12345", {"string": "listboxoptionAbbr"}]]
+ }, {
+ accOrElmOrID: "columnheader",
+ oldAccOrElmOrID: "grid",
+ expectedUtterance: [[{"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args" :[1]}, "Sunday"], ["Sunday",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args" :[1]}]],
+ expectedBraille: [[{"string": "cellInfoAbbr", "args": [1, 1]},
+ "Sunday"], ["Sunday", {"string": "cellInfoAbbr", "args": [1, 1]}]]
+ }, {
+ accOrElmOrID: "rowheader",
+ oldAccOrElmOrID: "grid",
+ expectedUtterance: [[{"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [2]}, "Sunday", "Week 1"], ["Week 1",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [2]}, "Sunday"]],
+ expectedBraille: [[{"string": "cellInfoAbbr", "args": [1, 2]},
+ "Sunday", "Week 1"], ["Week 1",
+ {"string": "cellInfoAbbr", "args": [1, 2]}, "Sunday"]]
+ }, {
+ accOrElmOrID: "gridcell1",
+ oldAccOrElmOrID: "grid",
+ expectedUtterance: [["3"], ["3"]],
+ expectedBraille: [["3"], ["3"]]
+ }, {
+ accOrElmOrID: "gridcell2",
+ oldAccOrElmOrID: "grid",
+ expectedUtterance: [["4", "7"], ["4", "7"]],
+ expectedBraille: [["4", "7"], ["4", "7"]]
+ }, {
+ accOrElmOrID: "gridcell3",
+ oldAccOrElmOrID: "grid",
+ expectedUtterance: [[{"string": "stateSelected"}, "5"],
+ ["5", {"string": "stateSelected"}]],
+ expectedBraille: [["5"], ["5"]],
+ }, {
+ accOrElmOrID: "frequency",
+ expectedUtterance: [[{"string": "stateCollapsed"},
+ {"string": "stateHasPopup"}, {"string": "combobox"}, "15 min"], [
+ "15 min", {"string": "stateCollapsed"}, {"string": "stateHasPopup"},
+ {"string": "combobox"}]],
+ expectedBraille: [[{"string": "comboboxAbbr"}, "15 min"], ["15 min",
+ {"string": "comboboxAbbr"}]]
+ }, {
+ accOrElmOrID: "selected-combobox-option",
+ oldAccOrElmOrID: "frequency",
+ expectedUtterance: [[{"string": "stateSelected"},
+ {"string": "comboboxoption"}, "15 min"], ["15 min",
+ {"string": "stateSelected"}, {"string": "comboboxoption"}]],
+ expectedBraille: [[{"string": "comboboxoptionAbbr"}, "15 min"], [
+ "15 min", {"string": "comboboxoptionAbbr"}]]
+ }, {
+ accOrElmOrID: "combobox-option",
+ oldAccOrElmOrID: "frequency",
+ expectedUtterance: [[{"string": "comboboxoption"}, "30 min"], [
+ "30 min", {"string": "comboboxoption"}]],
+ expectedBraille: [[{"string": "comboboxoptionAbbr"}, "30 min"], [
+ "30 min", {"string": "comboboxoptionAbbr"}]]
+ }, {
+ accOrElmOrID: "labelled-combobox",
+ expectedUtterance: [[{"string": "stateCollapsed"},
+ {"string": "stateHasPopup"}, {"string": "combobox"}, "Never",
+ "Intervals"], ["Intervals", "Never", {"string": "stateCollapsed"},
+ {"string": "stateHasPopup"}, {"string": "combobox"}]],
+ expectedBraille: [[{"string": "comboboxAbbr"}, "Never", "Intervals"],
+ ["Intervals", "Never", {"string": "comboboxAbbr"}]]
+ }, {
+ accOrElmOrID: "statusbar-1",
+ expectedUtterance: [["Last sync:", "2 days ago"],
+ ["Last sync:", "2 days ago"]],
+ expectedBraille: [["Last sync:", "2 days ago"],
+ ["Last sync:", "2 days ago"]]
+ }, {
+ accOrElmOrID: "statusbar-2",
+ expectedUtterance: [["Last sync: 30min ago"],
+ ["Last sync: 30min ago"]],
+ expectedBraille: [["Last sync: 30min ago"], ["Last sync: 30min ago"]]
+ }, {
+ accOrElmOrID: "switch-1",
+ expectedUtterance: [[{"string": "stateOn"}, {"string": "switch"},
+ "Simple switch"], ["Simple switch", {"string": "stateOn"},
+ {"string": "switch"}]],
+ expectedBraille: [[{"string": "stateCheckedAbbr"}, "Simple switch"],
+ ["Simple switch", {"string": "stateCheckedAbbr"}]]
+ }, {
+ accOrElmOrID: "switch-2",
+ expectedUtterance: [[{"string": "stateOff"},
+ {"string": "switch"}, "Another switch"], ["Another switch",
+ {"string": "stateOff"}, {"string": "switch"}]],
+ expectedBraille: [
+ [{"string": "stateUncheckedAbbr"}, "Another switch"],
+ ["Another switch", {"string": "stateUncheckedAbbr"}]]
+ }];
+
+ // Test all possible utterance order preference values.
+ function testOutputOrder(aOutputOrder) {
+ return function() {
+ SpecialPowers.pushPrefEnv({
+ "set": [[PREF_UTTERANCE_ORDER, aOutputOrder]]
+ }, function() {
+ tests.forEach(function run(test) {
+ testOutput(test.expectedUtterance[aOutputOrder], test.accOrElmOrID,
+ test.oldAccOrElmOrID, 1);
+ testOutput(test.expectedBraille[aOutputOrder], test.accOrElmOrID,
+ test.oldAccOrElmOrID, 0);
+ });
+ AccessFuTest.nextTest();
+ });
+ };
+ }
+
+ AccessFuTest.addFunc(testOutputOrder(0));
+ AccessFuTest.addFunc(testOutputOrder(1));
+ AccessFuTest.waitForExplicitFinish();
+ AccessFuTest.runTests();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+
+ </script>
+ </head>
+ <body>
+ <div id="root">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=753984"
+ title="[AccessFu] utterance order test">
+ Mozilla Bug 753984</a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=758675"
+ title="[AccessFu] Add support for accDescription">
+ Mozilla Bug 758675</a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=876475"
+ title="[AccessFu] Make braille output less verbose">
+ Mozilla Bug 876475</a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=924284"
+ title="[AccessFu] Output accessible values">
+ Mozilla Bug 924284</a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=925845"
+ title="[AccessFu] Unify output tests">
+ Mozilla Bug 925845</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ <a id="anchor" href="#test" title="title"></a>
+ <a id="anchor_titleandtext" href="#test" title="goes to the tests">Tests</a>
+ <a id="anchor_duplicatedtitleandtext" href="#test" title="Tests">Tests</a>
+ <a id="anchor_arialabelandtext" href="#test" aria-label="Tests" title="goes to the tests">Tests</a>
+ <textarea id="textarea" cols="80" rows="5">
+ This is the text area text.
+ </textarea>
+ <h1 id="heading" title="Test heading"></h1>
+ <ol id="list">
+ <li id="li_one">list one</li>
+ </ol>
+ <ul id="unorderd_list">
+ <li id="li_two">list two</li>
+ </ul>
+ <dl id="dlist">
+ <dd id="dd_one">
+ dd one
+ </dd>
+ </dl>
+ <table>
+ <caption>Fruits and vegetables</caption>
+ <tr>
+ <td id="cell">
+ <ul style="list-style-type: none;">
+ <li><a id="apples" href="#">Apples</a></li>
+ <li><a id="bananas" href="#">Bananas</a></li>
+ <li><a href="#">Peaches</a></li>
+ <li>
+ <a href="#">
+ Plums
+ </a>
+ </li>
+ </ul>
+ </td>
+ </tr>
+ </table>
+ <button id="unavailableButton" disabled>I am unavailable</button>
+ <button id="expandedButton" aria-expanded="true">I am expanded</button>
+ <button id="collapsedButton" aria-expanded="false">I am collapsed</button>
+ <input id="requiredInput" required placeholder="I am required" />
+ <input id="readonlyInput" readonly value="No edits" />
+ <textarea id="readonlyTextarea" readonly>No editing</textarea>
+ <button id="hasPopupButton" aria-haspopup="true">I have a popup</button>
+ <div role="tablist">
+ <a id="tab1" href="#" role="tab" aria-selected="true">Account</a>
+ <a id="tab2" href="#" role="tab" aria-selected="false">Advanced</a>
+ </div>
+ <form id="form1">
+ <label id="label1"><input id="input1" type="checkbox">Orange</label>
+ <input id="input2" type="checkbox"><label id="label2" for="input2">Blue</label>
+ </form>
+ <label id="label3">First name: <input id="input3" value="Joe"></label>
+ <label id="label4">Points:
+ <input id="input4" type="range" name="points" min="1" max="10" value="3">
+ </label>
+ <label for="input5">Boring label</label><input id="input5" type="checkbox" checked></input>
+ <label for="password">Secret Password</label><input id="password" type="password"></input>
+ <label for="radio_unselected">any old radio button</label><input id="radio_unselected" type="radio"></input>
+ <label for="radio_selected">a unique radio button</label><input id="radio_selected" type="radio" checked></input>
+ <input id="date" type="date" value="2011-09-29" />
+ <input id="email" type="email" value="test@example.com" />
+ <input id="search" type="search" value="This is a search" />
+ <input id="tel" type="tel" value="555-5555" />
+ <input id="url" type="url" value="http://example.com" />
+ <input id="textInput" type="text" value="This is text." />
+ <label>Points: <input id="range" type="range" name="points" min="1" max="10" value="3"></label>
+ <div id="togglebutton_notpressed" aria-pressed="false" role="button" tabindex="-1">I am not pressed</div>
+ <div id="togglebutton_pressed" aria-pressed="true" role="button" tabindex="-1">I am pressed!</div>
+ <ul role="listbox" style="list-style-type: none;">
+ <li role="option" id="listbox-option">Search suggestion</li>
+ <li role="option" id="listbox-option2">
+ <label aria-hidden="true">
+ <input type="checkbox" />
+ </label>
+ 555-12345
+ </li>
+ </ul>
+ <section id="grid" role="grid">
+ <ol role="row">
+ <li role="presentation"></li>
+ <li id="columnheader" role="columnheader" aria-label="Sunday">S</li>
+ <li role="columnheader">M</li>
+ </ol>
+ <ol role="row">
+ <li id="rowheader" role="rowheader" aria-label="Week 1">1</li>
+ <li id="gridcell1" role="gridcell"><span>3</span><div></div></li>
+ <li id="gridcell2" role="gridcell"><span>4</span><div>7</div></li>
+ </ol>
+ <ol role="row">
+ <li role="rowheader">2</li>
+ <li id="gridcell3" aria-selected="true" role="gridcell">5</li>
+ <li role="gridcell">6</li>
+ </ol>
+ </section>
+ <select id="frequency">
+ <option id="selected-combobox-option" value="15">15 min</option>
+ <option id="combobox-option" value="30">30 min</option>
+ <option value="null">Manual</option>
+ </select>
+ <select id="labelled-combobox" aria-label="Intervals">
+ <option value="15">Every 15 min</option>
+ <option value="30">Every 30 min</option>
+ <option value="null" selected>Never</option>
+ </select>
+ <div id="statusbar-1" role="status">Last sync:<span>2 days ago</span></div>
+ <div aria-label="Last sync: 30min ago" id="statusbar-2" role="status">I should be ignored</div>
+ <span id="switch-1" role="switch" aria-label="Simple switch" aria-checked="true"></span>
+ <span id="switch-2" role="switch" aria-label="Another switch" aria-checked="false"></span>
+ </div>
+ </body>
+</html>
diff --git a/accessible/tests/mochitest/jsat/test_output_mathml.html b/accessible/tests/mochitest/jsat/test_output_mathml.html
new file mode 100644
index 000000000..3fe4779b2
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/test_output_mathml.html
@@ -0,0 +1,313 @@
+<html>
+<head>
+ <title>[AccessFu] MathML Accessibility Support</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="output.js"></script>
+ <script type="application/javascript"
+ src="jsatcommon.js"></script>
+ <script type="application/javascript">
+
+ function doTest() {
+ // Test the following accOrElmOrID.
+ var tests = [{
+ accOrElmOrID: "math-1",
+ expectedUtterance: [
+ [{"string":"open-fence"},"(","x",",","y",{"string":"close-fence"},")"],
+ ["(",{"string":"open-fence"},"x",",","y",")",{"string":"close-fence"}]
+ ],
+ expectedBraille: [
+ [{"string":"open-fenceAbbr"},"(","x",",","y",{"string":"close-fenceAbbr"},")"],
+ ["(",{"string":"open-fenceAbbr"},"x",",","y",")",{"string":"close-fenceAbbr"}]
+ ]
+ }, {
+ accOrElmOrID: "mfrac-1",
+ expectedUtterance: [
+ [{"string":"mathmlfraction"},{"string":"numerator"},"a",{"string":"denominator"},"b"],
+ ["a",{"string":"numerator"},"b",{"string":"denominator"},{"string":"mathmlfraction"}]
+ ],
+ expectedBraille: [
+ [{"string":"mathmlfractionAbbr"},{"string":"numeratorAbbr"},"a",{"string":"denominatorAbbr"},"b"],
+ ["a",{"string":"numeratorAbbr"},"b",{"string":"denominatorAbbr"},{"string":"mathmlfractionAbbr"}]
+ ]
+ }, {
+ accOrElmOrID: "mfrac-2",
+ expectedUtterance: [
+ [{"string":"mathmlfractionwithoutbar"},{"string":"numerator"},"a",{"string":"denominator"},"b"],
+ ["a",{"string":"numerator"},"b",{"string":"denominator"},{"string":"mathmlfractionwithoutbar"}]
+ ],
+ expectedBraille: [
+ [{"string":"mathmlfractionwithoutbarAbbr"},{"string":"numeratorAbbr"},"a",{"string":"denominatorAbbr"},"b"],
+ ["a",{"string":"numeratorAbbr"},"b",{"string":"denominatorAbbr"},{"string":"mathmlfractionwithoutbarAbbr"}]
+ ]
+ }, {
+ accOrElmOrID: "msub-1",
+ expectedUtterance: [
+ [{"string":"mathmlscripted"},{"string":"base"},"a",{"string":"subscript"},"b"],
+ ["a",{"string":"base"},"b",{"string":"subscript"},{"string":"mathmlscripted"}]
+ ],
+ expectedBraille: [
+ [{"string":"mathmlscriptedAbbr"},{"string":"baseAbbr"},"a",{"string":"subscriptAbbr"},"b"],
+ ["a",{"string":"baseAbbr"},"b",{"string":"subscriptAbbr"},{"string":"mathmlscriptedAbbr"}]
+ ]
+ }, {
+ accOrElmOrID: "msup-1",
+ expectedUtterance: [
+ [{"string":"mathmlscripted"},{"string":"base"},"a",{"string":"superscript"},"b"],
+ ["a",{"string":"base"},"b",{"string":"superscript"},{"string":"mathmlscripted"}]
+ ],
+ expectedBraille: [
+ [{"string":"mathmlscriptedAbbr"},{"string":"baseAbbr"},"a",{"string":"superscriptAbbr"},"b"],
+ ["a",{"string":"baseAbbr"},"b",{"string":"superscriptAbbr"},{"string":"mathmlscriptedAbbr"}]
+ ]
+ }, {
+ accOrElmOrID: "msubsup-1",
+ expectedUtterance: [
+ [{"string":"mathmlscripted"},{"string":"base"},"a",{"string":"subscript"},"b",{"string":"superscript"},"c"],
+ ["a",{"string":"base"},"b",{"string":"subscript"},"c",{"string":"superscript"},{"string":"mathmlscripted"}]
+ ],
+ expectedBraille: [
+ [{"string":"mathmlscriptedAbbr"},{"string":"baseAbbr"},"a",{"string":"subscriptAbbr"},"b",{"string":"superscriptAbbr"},"c"],
+ ["a",{"string":"baseAbbr"},"b",{"string":"subscriptAbbr"},"c",{"string":"superscriptAbbr"},{"string":"mathmlscriptedAbbr"}]
+ ]
+ }, {
+ accOrElmOrID: "mmultiscripts-1",
+ expectedUtterance: [
+ [{"string":"mathmlscripted"},{"string":"base"},"a",{"string":"subscript"},"b",{"string":"superscript"},"c",{"string":"superscript"},"d",{"string":"presubscript"},"e",{"string":"presubscript"},"f",{"string":"presuperscript"},"g"],
+ ["a",{"string":"base"},"b",{"string":"subscript"},"c",{"string":"superscript"},"d",{"string":"superscript"},"e",{"string":"presubscript"},"f",{"string":"presubscript"},"g",{"string":"presuperscript"},{"string":"mathmlscripted"}]
+ ],
+ expectedBraille: [
+ [{"string":"mathmlscriptedAbbr"},{"string":"baseAbbr"},"a",{"string":"subscriptAbbr"},"b",{"string":"superscriptAbbr"},"c",{"string":"superscriptAbbr"},"d",{"string":"presubscriptAbbr"},"e",{"string":"presubscriptAbbr"},"f",{"string":"presuperscriptAbbr"},"g"],
+ ["a",{"string":"baseAbbr"},"b",{"string":"subscriptAbbr"},"c",{"string":"superscriptAbbr"},"d",{"string":"superscriptAbbr"},"e",{"string":"presubscriptAbbr"},"f",{"string":"presubscriptAbbr"},"g",{"string":"presuperscriptAbbr"},{"string":"mathmlscriptedAbbr"}]
+ ]
+ }, {
+ accOrElmOrID: "munder-1",
+ expectedUtterance: [
+ [{"string":"mathmlscripted"},{"string":"base"},"a",{"string":"underscript"},"b"],
+ ["a",{"string":"base"},"b",{"string":"underscript"},{"string":"mathmlscripted"}]
+ ],
+ expectedBraille: [
+ [{"string":"mathmlscriptedAbbr"},{"string":"baseAbbr"},"a",{"string":"underscriptAbbr"},"b"],
+ ["a",{"string":"baseAbbr"},"b",{"string":"underscriptAbbr"},{"string":"mathmlscriptedAbbr"}]
+ ]
+ }, {
+ accOrElmOrID: "mover-1",
+ expectedUtterance: [
+ [{"string":"mathmlscripted"},{"string":"base"},"a",{"string":"overscript"},"b"],
+ ["a",{"string":"base"},"b",{"string":"overscript"},{"string":"mathmlscripted"}]
+ ],
+ expectedBraille: [
+ [{"string":"mathmlscriptedAbbr"},{"string":"baseAbbr"},"a",{"string":"overscriptAbbr"},"b"],
+ ["a",{"string":"baseAbbr"},"b",{"string":"overscriptAbbr"},{"string":"mathmlscriptedAbbr"}]
+ ]
+ }, {
+ accOrElmOrID: "munderover-1",
+ expectedUtterance: [
+ [{"string":"mathmlscripted"},{"string":"base"},"a",{"string":"underscript"},"b",{"string":"overscript"},"c"],
+ ["a",{"string":"base"},"b",{"string":"underscript"},"c",{"string":"overscript"},{"string":"mathmlscripted"}]
+ ],
+ expectedBraille: [
+ [{"string":"mathmlscriptedAbbr"},{"string":"baseAbbr"},"a",{"string":"underscriptAbbr"},"b",{"string":"overscriptAbbr"},"c"],
+ ["a",{"string":"baseAbbr"},"b",{"string":"underscriptAbbr"},"c",{"string":"overscriptAbbr"},{"string":"mathmlscriptedAbbr"}]
+ ]
+ }, {
+ accOrElmOrID: "mroot-1",
+ expectedUtterance: [
+ [{"string":"mathmlroot"},{"string":"base"},"a",{"string":"root-index"},"b"],
+ ["a",{"string":"base"},"b",{"string":"root-index"},{"string":"mathmlroot"}]
+ ],
+ expectedBraille: [
+ [{"string":"mathmlrootAbbr"},{"string":"baseAbbr"},"a",{"string":"root-indexAbbr"},"b"],
+ ["a",{"string":"baseAbbr"},"b",{"string":"root-indexAbbr"},{"string":"mathmlrootAbbr"}]
+ ]
+ }, {
+ accOrElmOrID: "mtable-1",
+ expectedUtterance: [
+ [{"string":"mathmltable"},{"string":"tblColumnInfo","count":3},{"string":"tblRowInfo","count":2},{"string":"columnInfo","args":[1]},{"string":"rowInfo","args":[1]},"a",{"string":"columnInfo","args":[2]},{"string":"rowInfo","args":[1]},"b",{"string":"columnInfo","args":[3]},{"string":"rowInfo","args":[1]},"c",{"string":"columnInfo","args":[1]},{"string":"rowInfo","args":[2]},"d",{"string":"columnInfo","args":[2]},{"string":"rowInfo","args":[2]},"e",{"string":"columnInfo","args":[3]},{"string":"rowInfo","args":[2]},"f"],
+ ["a",{"string":"columnInfo","args":[1]},{"string":"rowInfo","args":[1]},"b",{"string":"columnInfo","args":[2]},{"string":"rowInfo","args":[1]},"c",{"string":"columnInfo","args":[3]},{"string":"rowInfo","args":[1]},"d",{"string":"columnInfo","args":[1]},{"string":"rowInfo","args":[2]},"e",{"string":"columnInfo","args":[2]},{"string":"rowInfo","args":[2]},"f",{"string":"columnInfo","args":[3]},{"string":"rowInfo","args":[2]},{"string":"mathmltable"},{"string":"tblColumnInfo","count":3},{"string":"tblRowInfo","count":2}]
+ ],
+ expectedBraille: [
+ [{"string":"mathmltableAbbr"},{"string":"tblColumnInfoAbbr","count":3},{"string":"tblRowInfoAbbr","count":2},{"string":"cellInfoAbbr","args":[1,1]},"a",{"string":"cellInfoAbbr","args":[2,1]},"b",{"string":"cellInfoAbbr","args":[3,1]},"c",{"string":"cellInfoAbbr","args":[1,2]},"d",{"string":"cellInfoAbbr","args":[2,2]},"e",{"string":"cellInfoAbbr","args":[3,2]},"f"],
+ ["a",{"string":"cellInfoAbbr","args":[1,1]},"b",{"string":"cellInfoAbbr","args":[2,1]},"c",{"string":"cellInfoAbbr","args":[3,1]},"d",{"string":"cellInfoAbbr","args":[1,2]},"e",{"string":"cellInfoAbbr","args":[2,2]},"f",{"string":"cellInfoAbbr","args":[3,2]},{"string":"mathmltableAbbr"},{"string":"tblColumnInfoAbbr","count":3},{"string":"tblRowInfoAbbr","count":2}]
+ ]
+ }, {
+ accOrElmOrID: "menclose-1",
+ expectedUtterance: [
+ [{"string":"mathmlenclosed"},{"string":"notation-longdiv"},"a"],
+ ["a",{"string":"notation-longdiv"},{"string":"mathmlenclosed"}]
+ ],
+ expectedBraille: [
+ [{"string":"mathmlenclosedAbbr"},{"string":"notation-longdivAbbr"},"a"],
+ ["a",{"string":"notation-longdivAbbr"},{"string":"mathmlenclosedAbbr"}]
+ ]
+ }, {
+ accOrElmOrID: "menclose-2",
+ expectedUtterance: [
+ [{"string":"mathmlenclosed"},{"string":"notation-circle"},"a"],
+ ["a",{"string":"notation-circle"},{"string":"mathmlenclosed"}]
+ ],
+ expectedBraille: [
+ [{"string":"mathmlenclosedAbbr"},{"string":"notation-circleAbbr"},"a"],
+ ["a",{"string":"notation-circleAbbr"},{"string":"mathmlenclosedAbbr"}]
+ ]
+ }, {
+ accOrElmOrID: "menclose-3",
+ expectedUtterance: [
+ [{"string":"mathmlenclosed"},{"string":"notation-left"},{"string":"notation-top"},{"string":"notation-bottom"},"a"],
+ ["a",{"string":"notation-left"},{"string":"notation-top"},{"string":"notation-bottom"},{"string":"mathmlenclosed"}]
+ ],
+ expectedBraille: [
+ [{"string":"mathmlenclosedAbbr"},{"string":"notation-leftAbbr"},{"string":"notation-topAbbr"},{"string":"notation-bottomAbbr"},"a"],
+ ["a",{"string":"notation-leftAbbr"},{"string":"notation-topAbbr"},{"string":"notation-bottomAbbr"},{"string":"mathmlenclosedAbbr"}]
+ ]
+ }];
+
+ // Test all possible utterance order preference values.
+ function testOutputOrder(aOutputOrder) {
+ return function() {
+ SpecialPowers.pushPrefEnv({
+ "set": [[PREF_UTTERANCE_ORDER, aOutputOrder]]
+ }, function() {
+ tests.forEach(function run(test) {
+ testOutput(test.expectedUtterance[aOutputOrder], test.accOrElmOrID,
+ test.oldAccOrElmOrID, 1);
+ testOutput(test.expectedBraille[aOutputOrder], test.accOrElmOrID,
+ test.oldAccOrElmOrID, 0);
+ });
+ AccessFuTest.nextTest();
+ });
+ };
+ }
+
+ AccessFuTest.addFunc(testOutputOrder(0));
+ AccessFuTest.addFunc(testOutputOrder(1));
+ AccessFuTest.waitForExplicitFinish();
+ AccessFuTest.runTests();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+ <div id="root">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1163374"
+ title="[AccessFu] MathML Accessibility Support">
+ Mozilla Bug 1163374
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+
+ <math id="math-1"><mo>(</mo><mi>x</mi><mo>,</mo><mi>y</mi><mo>)</mo></math>
+
+ <math>
+ <mfrac id="mfrac-1">
+ <mi>a</mi>
+ <mi>b</mi>
+ </mfrac>
+ </math>
+
+ <math>
+ <mfrac id="mfrac-2" linethickness="0px">
+ <mi>a</mi>
+ <mi>b</mi>
+ </mfrac>
+ </math>
+
+ <math>
+ <msub id="msub-1">
+ <mi>a</mi>
+ <mi>b</mi>
+ </msub>
+ </math>
+ <math>
+ <msup id="msup-1">
+ <mi>a</mi>
+ <mi>b</mi>
+ </msup>
+ </math>
+ <math>
+ <msubsup id="msubsup-1">
+ <mi>a</mi>
+ <mi>b</mi>
+ <mi>c</mi>
+ </msubsup>
+ </math>
+ <math>
+ <mmultiscripts id="mmultiscripts-1">
+ <mi>a</mi>
+ <mi>b</mi>
+ <mi>c</mi>
+ <none/>
+ <mi>d</mi>
+ <mprescripts/>
+ <mi>e</mi>
+ <none/>
+ <mi>f</mi>
+ <mi>g</mi>
+ </mmultiscripts>
+ </math>
+
+ <math>
+ <munder id="munder-1">
+ <mi>a</mi>
+ <mi>b</mi>
+ </munder>
+ </math>
+ <math>
+ <mover id="mover-1">
+ <mi>a</mi>
+ <mi>b</mi>
+ </mover>
+ </math>
+ <math>
+ <munderover id="munderover-1">
+ <mi>a</mi>
+ <mi>b</mi>
+ <mi>c</mi>
+ </munderover>
+ </math>
+
+ <math>
+ <mroot id="mroot-1">
+ <mi>a</mi>
+ <mi>b</mi>
+ </mroot>
+ </math>
+
+ <math>
+ <mtable id="mtable-1">
+ <mtr>
+ <mtd><mi>a</mi></mtd>
+ <mtd><mi>b</mi></mtd>
+ <mtd><mi>c</mi></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mi>d</mi></mtd>
+ <mtd><mi>e</mi></mtd>
+ <mtd><mi>f</mi></mtd>
+ </mtr>
+ </mtable>
+ </math>
+
+ <math>
+ <menclose id="menclose-1"><mi>a</mi></menclose>
+ </math>
+ <math>
+ <menclose id="menclose-2" notation="circle"><mi>a</mi></menclose>
+ </math>
+ <math>
+ <menclose id="menclose-3" notation="left top bottom"><mi>a</mi></menclose>
+ </math>
+
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/jsat/test_pointer_relay.html b/accessible/tests/mochitest/jsat/test_pointer_relay.html
new file mode 100644
index 000000000..cb58fe73b
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/test_pointer_relay.html
@@ -0,0 +1,95 @@
+<html>
+
+<head>
+ <title>AccessFu tests for pointer relay.</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="../common.js"></script>
+ <script type="application/javascript" src="../layout.js"></script>
+ <script type="application/javascript" src="./jsatcommon.js"></script>
+ <script type="application/javascript" src="./dom_helper.js"></script>
+ <script type="application/javascript">
+
+ Components.utils.import(
+ "resource://gre/modules/accessibility/PointerAdapter.jsm");
+
+ var tests = [
+ {
+ type: 'touchstart', target: [{base: 'button'}],
+ expected: {type: 'pointerdown', length: 1}
+ },
+ {
+ type: 'touchmove', target: [{base: 'button'}],
+ expected: {type: 'pointermove', length: 1}
+ },
+ {
+ type: 'touchend', target: [{base: 'button'}],
+ expected: {type: 'pointerup'}
+ },
+ {
+ type: 'touchstart', target: [{base: 'button'},
+ {base: 'button', x: 0.5, y: 0.3}],
+ expected: {type: 'pointerdown', length: 2}
+ },
+ {
+ type: 'touchend', target: [{base: 'button'},
+ {base: 'button', x: 0.5, y: 0.3}],
+ expected: {type: 'pointerup'}
+ },
+ {
+ type: 'touchstart', target: [{base: 'button'},
+ {base: 'button', x: 0.5, y: 0.3},
+ {base: 'button', x: 0.5, y: -0.3}],
+ expected: {type: 'pointerdown', length: 3}
+ },
+ {
+ type: 'touchend', target: [{base: 'button'},
+ {base: 'button', x: 0.5, y: 0.3},
+ {base: 'button', x: 0.5, y: -0.3}],
+ expected: {type: 'pointerup'}
+ }
+ ];
+
+ function makeTestFromSpec(test) {
+ return function runTest() {
+ PointerRelay.start(function onPointerEvent(aDetail) {
+ is(aDetail.type, test.expected.type,
+ 'mozAccessFuPointerEvent is correct.');
+ if (test.expected.length) {
+ is(aDetail.points.length, test.expected.length,
+ 'mozAccessFuPointerEvent points length is correct.');
+ }
+ PointerRelay.stop();
+ AccessFuTest.nextTest();
+ });
+ eventMap[test.type](test.target, test.type);
+ };
+ }
+
+ function doTest() {
+ tests.forEach(function addTest(test) {
+ AccessFuTest.addFunc(makeTestFromSpec(test));
+ });
+ AccessFuTest.waitForExplicitFinish();
+ AccessFuTest.runTests();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+<body id="root">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=976082"
+ title="[AccessFu] Provide tests for pointer relay.">
+ Mozilla Bug 981015
+ </a>
+ <div>
+ <button id="button">I am a button</button>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/jsat/test_quicknav_modes.html b/accessible/tests/mochitest/jsat/test_quicknav_modes.html
new file mode 100644
index 000000000..f99b64a84
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/test_quicknav_modes.html
@@ -0,0 +1,107 @@
+<html>
+
+<head>
+ <title>AccessFu test for enabling</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="./jsatcommon.js"></script>
+ <script type="application/javascript">
+
+ function prefStart() {
+ // Start AccessFu via pref.
+ SpecialPowers.pushPrefEnv({"set": [['accessibility.accessfu.activate', 1]]});
+ AccessFuTest.once_log("EventManager.start", AccessFuTest.nextTest);
+ }
+
+ function nextMode(aCurrentMode, aNextMode) {
+ return function() {
+ is(AccessFu.Input.quickNavMode.current, aCurrentMode,
+ 'initial current mode is correct');
+ AccessFu.Input.quickNavMode.next();
+ _expectMode(aNextMode, AccessFuTest.nextTest);
+ }
+ }
+
+ function prevMode(aCurrentMode, aNextMode) {
+ return function() {
+ is(AccessFu.Input.quickNavMode.current, aCurrentMode,
+ 'initial current mode is correct');
+ AccessFu.Input.quickNavMode.previous();
+ _expectMode(aNextMode, AccessFuTest.nextTest);
+ }
+ }
+
+ function setMode(aModeIndex, aExpectedMode) {
+ return function() {
+ SpecialPowers.pushPrefEnv(
+ {"set": [['accessibility.accessfu.quicknav_index', aModeIndex]]},
+ function() {
+ _expectMode(aExpectedMode, AccessFuTest.nextTest);
+ });
+ }
+ }
+
+ function reconfigureModes() {
+ SpecialPowers.pushPrefEnv(
+ {"set": [['accessibility.accessfu.quicknav_modes', 'Landmark,Button,Entry,Graphic']]},
+ function() {
+ // When the modes are reconfigured, the current mode should
+ // be set to the first in the new list.
+ _expectMode('Landmark', AccessFuTest.nextTest);
+ });
+ }
+
+ function _expectMode(aExpectedMode, aCallback) {
+ if (AccessFu.Input.quickNavMode.current === aExpectedMode) {
+ ok(true, 'correct mode');
+ aCallback();
+ } else {
+ AccessFuTest.once_log('Quicknav mode: ' + aExpectedMode, function() {
+ ok(true, 'correct mode');
+ aCallback();
+ });
+ }
+ }
+
+ // Listen for initial 'EventManager.start' and disable AccessFu.
+ function prefStop() {
+ ok(AccessFu._enabled, "AccessFu was started via preference.");
+ AccessFuTest.once_log("EventManager.stop", () => AccessFuTest.finish());
+ SpecialPowers.pushPrefEnv({"set": [['accessibility.accessfu.activate', 0]]});
+ }
+
+ function doTest() {
+ AccessFuTest.addFunc(prefStart);
+ AccessFuTest.addFunc(nextMode('Link', 'Heading'));
+ AccessFuTest.addFunc(nextMode('Heading', 'FormElement'));
+ AccessFuTest.addFunc(nextMode('FormElement', 'Link'));
+ AccessFuTest.addFunc(nextMode('Link', 'Heading'));
+ AccessFuTest.addFunc(prevMode('Heading', 'Link'));
+ AccessFuTest.addFunc(prevMode('Link', 'FormElement'));
+ AccessFuTest.addFunc(setMode(1, 'Heading'));
+ AccessFuTest.addFunc(reconfigureModes);
+ AccessFuTest.addFunc(prefStop);
+ AccessFuTest.waitForExplicitFinish();
+ AccessFuTest.runTests([ // Will call SimpleTest.finish();
+ ['accessibility.accessfu.quicknav_modes', 'Link,Heading,FormElement']]);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=811307"
+ title="[AccessFu] Add mochitest for enabling">
+ Mozilla Bug 811307
+ </a>
+</body>
+</html> \ No newline at end of file
diff --git a/accessible/tests/mochitest/jsat/test_tables.html b/accessible/tests/mochitest/jsat/test_tables.html
new file mode 100644
index 000000000..aa7f482e9
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/test_tables.html
@@ -0,0 +1,579 @@
+<html>
+<head>
+ <title>[AccessFu] Improve reading of table semantics</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="output.js"></script>
+ <script type="application/javascript"
+ src="jsatcommon.js"></script>
+ <script type="application/javascript">
+
+ function doTest() {
+ // Test the following accOrElmOrID.
+ var tests = [{
+ accOrElmOrID: "table1",
+ expectedUtterance: [[
+ {"string": "table"},
+ {"string": "tblColumnInfo", "count": 2},
+ {"string": "tblRowInfo", "count": 2},
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [1]}, "col1",
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [1]}, "col2",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [2]}, "col1", "cell1",
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [2]}, "col2", "cell2"], ["col1",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [1]}, "col2",
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [1]}, "cell1",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [2]}, "col1", "cell2",
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [2]}, "col2", {"string": "table"},
+ {"string": "tblColumnInfo", "count": 2},
+ {"string": "tblRowInfo", "count": 2}]],
+ expectedBraille: [[
+ {"string": "tableAbbr"},
+ {"string": "tblColumnInfoAbbr", "count": 2},
+ {"string": "tblRowInfoAbbr", "count": 2},
+ {"string": "cellInfoAbbr", "args": [1, 1]}, "col1",
+ {"string": "cellInfoAbbr", "args": [2, 1]}, "col2",
+ {"string": "cellInfoAbbr", "args": [1, 2]}, "col1", "cell1",
+ {"string": "cellInfoAbbr", "args": [2, 2]}, "col2", "cell2"], ["col1",
+ {"string": "cellInfoAbbr", "args": [1, 1]}, "col2",
+ {"string": "cellInfoAbbr", "args": [2, 1]}, "cell1",
+ {"string": "cellInfoAbbr", "args": [1, 2]}, "col1", "cell2",
+ {"string": "cellInfoAbbr", "args": [2, 2]}, "col2",
+ {"string": "tableAbbr"},
+ {"string": "tblColumnInfoAbbr", "count": 2},
+ {"string": "tblRowInfoAbbr", "count": 2}]]
+ }, {
+ accOrElmOrID: "table2",
+ expectedUtterance: [[
+ {"string": "table"},
+ {"string": "tblColumnInfo", "count": 2},
+ {"string": "tblRowInfo", "count": 2},
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [1]}, "col1", "cell1",
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [1]}, "col2",
+ {"string": "table"},
+ {"string": "tblColumnInfo", "count": 1},
+ {"string": "tblRowInfo", "count": 2},
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [1]}, "colheader",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [2]}, "colheader", "bla",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [2]}, "col1",
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [2]}, "col2"], ["cell1",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [1]}, "col1", "colheader",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [1]}, "bla",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [2]}, "colheader",
+ {"string": "table"},
+ {"string": "tblColumnInfo", "count": 1},
+ {"string": "tblRowInfo", "count": 2},
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [1]}, "col2", "col1",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [2]}, "col2",
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [2]}, {"string": "table"},
+ {"string": "tblColumnInfo", "count": 2},
+ {"string": "tblRowInfo", "count": 2}]],
+ expectedBraille: [[{"string": "tableAbbr"},
+ {"string": "tblColumnInfoAbbr", "count": 2},
+ {"string": "tblRowInfoAbbr", "count": 2},
+ {"string": "cellInfoAbbr", "args": [1, 1]}, "col1", "cell1",
+ {"string": "cellInfoAbbr", "args": [2, 1]}, "col2",
+ {"string": "tableAbbr"},
+ {"string": "tblColumnInfoAbbr", "count": 1},
+ {"string": "tblRowInfoAbbr", "count": 2},
+ {"string": "cellInfoAbbr", "args": [1, 1]}, "colheader",
+ {"string": "cellInfoAbbr", "args": [1, 2]}, "colheader", "bla",
+ {"string": "cellInfoAbbr", "args": [1, 2]}, "col1",
+ {"string": "cellInfoAbbr", "args": [2, 2]}, "col2"], ["cell1",
+ {"string": "cellInfoAbbr", "args": [1, 1]}, "col1", "colheader",
+ {"string": "cellInfoAbbr", "args": [1, 1]}, "bla",
+ {"string": "cellInfoAbbr", "args": [1, 2]}, "colheader",
+ {"string": "tableAbbr"},
+ {"string": "tblColumnInfoAbbr", "count": 1},
+ {"string": "tblRowInfoAbbr", "count": 2},
+ {"string": "cellInfoAbbr", "args": [2, 1]}, "col2", "col1",
+ {"string": "cellInfoAbbr", "args": [1, 2]}, "col2",
+ {"string": "cellInfoAbbr", "args": [2, 2]},
+ {"string": "tableAbbr"},
+ {"string": "tblColumnInfoAbbr", "count": 2},
+ {"string": "tblRowInfoAbbr", "count": 2}]]
+ }, {
+ accOrElmOrID: "table3",
+ expectedUtterance: [[
+ {"string": "table"},
+ {"string": "tblColumnInfo", "count": 2},
+ {"string": "tblRowInfo", "count": 2},
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [1]}, "col2",
+ {"string": "table"},
+ {"string": "tblColumnInfo", "count": 1},
+ {"string": "tblRowInfo", "count": 2},
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [1]}, "colheader",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [2]}, "colheader", "bla"], ["colheader",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [1]}, "bla",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [2]}, "colheader",
+ {"string": "table"},
+ {"string": "tblColumnInfo", "count": 1},
+ {"string": "tblRowInfo", "count": 2},
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [1]}, "col2",
+ {"string": "table"},
+ {"string": "tblColumnInfo", "count": 2},
+ {"string": "tblRowInfo", "count": 2}]],
+ expectedBraille: [[
+ {"string": "tableAbbr"},
+ {"string": "tblColumnInfoAbbr", "count": 1},
+ {"string": "tblRowInfoAbbr", "count": 2},
+ {"string": "cellInfoAbbr", "args": [1, 1]}, "colheader",
+ {"string": "cellInfoAbbr", "args": [1, 2]}, "colheader", "bla"],
+ ["colheader",
+ {"string": "cellInfoAbbr", "args": [1, 1]}, "bla",
+ {"string": "cellInfoAbbr", "args": [1, 2]}, "colheader",
+ {"string": "tableAbbr"},
+ {"string": "tblColumnInfoAbbr", "count": 1},
+ {"string": "tblRowInfoAbbr", "count": 2}]]
+ }, {
+ accOrElmOrID: "table4",
+ expectedUtterance: [[
+ {"string": "table"},
+ {"string": "tblColumnInfo", "count": 4},
+ {"string": "tblRowInfo", "count": 3},
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [1]}, "col1",
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [1]}, "col2",
+ {"string": "columnInfo", "args": [3]},
+ {"string": "rowInfo", "args": [1]}, "col3",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [2]},
+ {"string": "spansColumns", "args": [2]}, "col1", "row1",
+ {"string": "columnInfo", "args": [3]},
+ {"string": "rowInfo", "args": [2]}, "col3", "row1", "cell1",
+ {"string": "columnInfo", "args": [4]},
+ {"string": "rowInfo", "args": [2]},
+ {"string": "spansRows", "args": [2]}, "row1", "cell2",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [3]}, "col1", "row2",
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [3]}, "col2", "row2", "cell3",
+ {"string": "columnInfo", "args": [3]},
+ {"string": "rowInfo", "args": [3]}, "col3", "row2", "cell4"], ["col1",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [1]}, "col2",
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [1]}, "col3",
+ {"string": "columnInfo", "args": [3]},
+ {"string": "rowInfo", "args": [1]}, "row1",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [2]},
+ {"string": "spansColumns", "args": [2]}, "col1", "cell1",
+ {"string": "columnInfo", "args": [3]},
+ {"string": "rowInfo", "args": [2]}, "col3", "row1", "cell2",
+ {"string": "columnInfo", "args": [4]},
+ {"string": "rowInfo", "args": [2]},
+ {"string": "spansRows", "args": [2]}, "row1", "row2",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [3]}, "col1", "cell3",
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [3]}, "col2", "row2", "cell4",
+ {"string": "columnInfo", "args": [3]},
+ {"string": "rowInfo", "args": [3]}, "col3", "row2",
+ {"string": "table"},
+ {"string": "tblColumnInfo", "count": 4},
+ {"string": "tblRowInfo", "count": 3}]],
+ expectedBraille: [[
+ {"string": "tableAbbr"},
+ {"string": "tblColumnInfoAbbr", "count": 4},
+ {"string": "tblRowInfoAbbr", "count": 3},
+ {"string": "cellInfoAbbr", "args": [1, 1]}, "col1",
+ {"string": "cellInfoAbbr", "args": [2, 1]}, "col2",
+ {"string": "cellInfoAbbr", "args": [3, 1]}, "col3",
+ {"string": "cellInfoAbbr", "args": [1, 2]}, "col1", "row1",
+ {"string": "cellInfoAbbr", "args": [3, 2]}, "col3", "row1", "cell1",
+ {"string": "cellInfoAbbr", "args": [4, 2]}, "row1", "cell2",
+ {"string": "cellInfoAbbr", "args": [1, 3]}, "col1", "row2",
+ {"string": "cellInfoAbbr", "args": [2, 3]}, "col2", "row2", "cell3",
+ {"string": "cellInfoAbbr", "args": [3, 3]}, "col3", "row2", "cell4"],
+ ["col1",
+ {"string": "cellInfoAbbr", "args": [1, 1]}, "col2",
+ {"string": "cellInfoAbbr", "args": [2, 1]}, "col3",
+ {"string": "cellInfoAbbr", "args": [3, 1]}, "row1",
+ {"string": "cellInfoAbbr", "args": [1, 2]}, "col1", "cell1",
+ {"string": "cellInfoAbbr", "args": [3, 2]}, "col3", "row1", "cell2",
+ {"string": "cellInfoAbbr", "args": [4, 2]}, "row1", "row2",
+ {"string": "cellInfoAbbr", "args": [1, 3]}, "col1", "cell3",
+ {"string": "cellInfoAbbr", "args": [2, 3]}, "col2", "row2", "cell4",
+ {"string": "cellInfoAbbr", "args": [3, 3]}, "col3", "row2",
+ {"string": "tableAbbr"},
+ {"string": "tblColumnInfoAbbr", "count": 4},
+ {"string": "tblRowInfoAbbr", "count": 3}]]
+ }, {
+ accOrElmOrID: "table5",
+ expectedUtterance: [["Row1", "Row2"], ["Row1", "Row2"]],
+ expectedBraille: [["Row1", "Row2"], ["Row1", "Row2"]]
+ }, {
+ // Test pivot to table1_th1 from table1.
+ accOrElmOrID: "table1_th1",
+ oldAccOrElmOrID: "table1",
+ expectedUtterance: [[
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [1]}, "col1"], ["col1",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [1]}]],
+ expectedBraille: [[
+ {"string": "cellInfoAbbr", "args": [1, 1]}, "col1"], ["col1",
+ {"string": "cellInfoAbbr", "args": [1, 1]}]]
+ }, {
+ // Test pivot to table1_td2 from table1.
+ accOrElmOrID: "table1_td2",
+ oldAccOrElmOrID: "table1",
+ expectedUtterance: [[
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [2]}, "col2", "cell2"], ["cell2",
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [2]}, "col2"]],
+ expectedBraille: [
+ [{"string": "cellInfoAbbr", "args": [2, 2]}, "col2", "cell2"],
+ ["cell2", {"string": "cellInfoAbbr", "args": [2, 2]}, "col2"]]
+ }, {
+ // Test pivot to table1_td2 from table1_th1.
+ accOrElmOrID: "table1_td2",
+ oldAccOrElmOrID: "table1_th1",
+ expectedUtterance: [[
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [2]}, "col2", "cell2"], ["cell2",
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [2]}, "col2"]],
+ expectedBraille: [
+ [{"string": "cellInfoAbbr", "args": [2, 2]}, "col2", "cell2"],
+ ["cell2", {"string": "cellInfoAbbr", "args": [2, 2]}, "col2"]]
+ }, {
+ // Test pivot to table1_td2 from table1_td1.
+ accOrElmOrID: "table1_td2",
+ oldAccOrElmOrID: "table1_td1",
+ expectedUtterance: [[
+ {"string": "columnInfo", "args": [2]}, "col2", "cell2"], ["cell2",
+ {"string": "columnInfo", "args": [2]}, "col2"]],
+ expectedBraille: [
+ [{"string": "cellInfoAbbr", "args": [2, 2]}, "col2", "cell2"],
+ ["cell2", {"string": "cellInfoAbbr", "args": [2, 2]}, "col2"]]
+ }, {
+ // Test pivot to table2_cell_1 from table2.
+ accOrElmOrID: "table2_cell_1",
+ oldAccOrElmOrID: "table2",
+ expectedUtterance: [[
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [1]}, "col1", "cell1"], ["cell1",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [1]}, "col1"]],
+ expectedBraille: [
+ [{"string": "cellInfoAbbr", "args": [1, 1]}, "col1", "cell1"],
+ ["cell1", {"string": "cellInfoAbbr", "args": [1, 1]}, "col1"]]
+ }, {
+ // Test pivot to table2_cell_2 from table2.
+ accOrElmOrID: "table2_cell_2",
+ oldAccOrElmOrID: "table2",
+ expectedUtterance: [[
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [1]}, "col2",
+ {"string": "table"},
+ {"string": "tblColumnInfo", "count": 1},
+ {"string": "tblRowInfo", "count": 2},
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [1]}, "colheader",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [2]}, "colheader", "bla"], ["colheader",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [1]}, "bla",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [2]}, "colheader",
+ {"string": "table"},
+ {"string": "tblColumnInfo", "count": 1},
+ {"string": "tblRowInfo", "count": 2},
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [1]}, "col2"]],
+ expectedBraille: [[
+ {"string": "cellInfoAbbr", "args": [2, 1]}, "col2",
+ {"string": "tableAbbr"},
+ {"string": "tblColumnInfoAbbr", "count": 1},
+ {"string": "tblRowInfoAbbr", "count": 2},
+ {"string": "cellInfoAbbr", "args": [1, 1]}, "colheader",
+ {"string": "cellInfoAbbr", "args": [1, 2]}, "colheader", "bla"],
+ ["colheader",
+ {"string": "cellInfoAbbr", "args": [1, 1]}, "bla",
+ {"string": "cellInfoAbbr", "args": [1, 2]}, "colheader",
+ {"string": "tableAbbr"},
+ {"string": "tblColumnInfoAbbr", "count": 1},
+ {"string": "tblRowInfoAbbr", "count": 2},
+ {"string": "cellInfoAbbr", "args": [2, 1]}, "col2"]]
+ }, {
+ // Test pivot to table2_cell_1 from table2_cell_2.
+ accOrElmOrID: "table2_cell_1",
+ oldAccOrElmOrID: "table2_cell_2",
+ expectedUtterance: [[
+ {"string": "columnInfo", "args": [1]}, "col1", "cell1"], ["cell1",
+ {"string": "columnInfo", "args": [1]}, "col1"]],
+ expectedBraille: [
+ [{"string": "cellInfoAbbr", "args": [1, 1]}, "col1", "cell1"],
+ ["cell1", {"string": "cellInfoAbbr", "args": [1, 1]}, "col1"]]
+ }, {
+ // Test pivot to table3_cell from table2.
+ accOrElmOrID: "table3_cell",
+ oldAccOrElmOrID: "table2",
+ expectedUtterance: [[
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [1]}, "col2",
+ {"string": "table"},
+ {"string": "tblColumnInfo", "count": 1},
+ {"string": "tblRowInfo", "count": 2},
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [2]}, "colheader", "bla"], ["bla",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [2]}, "colheader",
+ {"string": "table"},
+ {"string": "tblColumnInfo", "count": 1},
+ {"string": "tblRowInfo", "count": 2},
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [1]}, "col2"]],
+ expectedBraille: [
+ [{"string": "cellInfoAbbr", "args": [1, 2]}, "colheader", "bla"],
+ ["bla", {"string": "cellInfoAbbr", "args": [1, 2]}, "colheader"]]
+ }, {
+ // Test pivot to table3_cell from table2_cell_1.
+ accOrElmOrID: "table3_cell",
+ oldAccOrElmOrID: "table2_cell_1",
+ expectedUtterance: [[
+ {"string": "columnInfo", "args": [2]}, "col2",
+ {"string": "table"},
+ {"string": "tblColumnInfo", "count": 1},
+ {"string": "tblRowInfo", "count": 2},
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [2]}, "colheader", "bla"], ["bla",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [2]}, "colheader",
+ {"string": "table"},
+ {"string": "tblColumnInfo", "count": 1},
+ {"string": "tblRowInfo", "count": 2},
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [1]}, "col2"]],
+ expectedBraille: [
+ [{"string": "cellInfoAbbr", "args": [1, 2]}, "colheader", "bla"],
+ ["bla", {"string": "cellInfoAbbr", "args": [1, 2]}, "colheader"]]
+ }, {
+ // Test pivot to table3_cell from table3_ch.
+ accOrElmOrID: "table3_cell",
+ oldAccOrElmOrID: "table3_ch",
+ expectedUtterance: [[
+ {"string": "rowInfo", "args": [2]}, "bla"], ["bla",
+ {"string": "rowInfo", "args": [2]}]],
+ expectedBraille: [
+ [{"string": "cellInfoAbbr", "args": [1, 2]}, "bla"],
+ ["bla", {"string": "cellInfoAbbr", "args": [1, 2]}]]
+ }, {
+ // Test pivot to table3_cell from table1_td1.
+ accOrElmOrID: "table3_cell",
+ oldAccOrElmOrID: "table1_td1",
+ expectedUtterance: [[
+ {"string": "table"},
+ {"string": "tblColumnInfo", "count": 2},
+ {"string": "tblRowInfo", "count": 2},
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [1]}, "col2",
+ {"string": "table"},
+ {"string": "tblColumnInfo", "count": 1},
+ {"string": "tblRowInfo", "count": 2},
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [2]}, "colheader", "bla"], ["bla",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [2]}, "colheader",
+ {"string": "table"},
+ {"string": "tblColumnInfo", "count": 1},
+ {"string": "tblRowInfo", "count": 2},
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [1]}, "col2",
+ {"string": "table"},
+ {"string": "tblColumnInfo", "count": 2},
+ {"string": "tblRowInfo", "count": 2}]],
+ expectedBraille: [
+ [{"string": "cellInfoAbbr", "args": [1, 2]}, "colheader", "bla"],
+ ["bla", {"string": "cellInfoAbbr", "args": [1, 2]}, "colheader"]]
+ }, {
+ // Test pivot to table4_ch_3 from table4.
+ accOrElmOrID: "table4_ch_3",
+ oldAccOrElmOrID: "table4",
+ expectedUtterance: [[
+ {"string": "columnInfo", "args": [3]},
+ {"string": "rowInfo", "args": [1]}, "col3"], ["col3",
+ {"string": "columnInfo", "args": [3]},
+ {"string": "rowInfo", "args": [1]}]],
+ expectedBraille: [
+ [{"string": "cellInfoAbbr", "args": [3, 1]}, "col3"],
+ ["col3", {"string": "cellInfoAbbr", "args": [3, 1]}]]
+ }, {
+ // Test pivot to table4_rh_1 from table4_ch_3.
+ accOrElmOrID: "table4_rh_1",
+ oldAccOrElmOrID: "table4_ch_3",
+ expectedUtterance: [[
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [2]},
+ {"string": "spansColumns", "args": [2]}, "col1", "row1"], ["row1",
+ {"string": "columnInfo", "args": [1]},
+ {"string": "rowInfo", "args": [2]},
+ {"string": "spansColumns", "args": [2]}, "col1"]],
+ expectedBraille: [
+ [{"string": "cellInfoAbbr", "args": [1, 2]}, "col1", "row1"],
+ ["row1", {"string": "cellInfoAbbr", "args": [1, 2]}, "col1"]]
+ }, {
+ // Test pivot to table4_cell_3 from table4_rh_1.
+ accOrElmOrID: "table4_cell_3",
+ oldAccOrElmOrID: "table4_rh_1",
+ expectedUtterance: [[
+ {"string": "columnInfo", "args": [4]},
+ {"string": "spansRows", "args": [2]}, "cell2"], ["cell2",
+ {"string": "columnInfo", "args": [4]},
+ {"string": "spansRows", "args": [2]}]],
+ expectedBraille: [
+ [{"string": "cellInfoAbbr", "args": [4, 2]}, "cell2"],
+ ["cell2", {"string": "cellInfoAbbr", "args": [4, 2]}]]
+ }, {
+ // Test pivot to table4_cell_5 from table4_cell_3.
+ accOrElmOrID: "table4_cell_5",
+ oldAccOrElmOrID: "table4_cell_3",
+ expectedUtterance: [[
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [3]}, "col2", "row2", "cell3"],
+ ["cell3",
+ {"string": "columnInfo", "args": [2]},
+ {"string": "rowInfo", "args": [3]}, "col2", "row2"]],
+ expectedBraille: [
+ [{"string": "cellInfoAbbr", "args": [2, 3]}, "col2", "row2", "cell3"],
+ ["cell3", {"string": "cellInfoAbbr", "args": [2, 3]}, "col2", "row2"]]
+ }];
+
+ // Test outputs (utterance and braille) for tables including their
+ // headers and cells.
+ function testOutputOrder(aOutputOrder) {
+ return function() {
+ SpecialPowers.pushPrefEnv({
+ "set": [[PREF_UTTERANCE_ORDER, aOutputOrder]]
+ }, function() {
+ tests.forEach(function run(test) {
+ testOutput(test.expectedUtterance[aOutputOrder], test.accOrElmOrID,
+ test.oldAccOrElmOrID, 1);
+ testOutput(test.expectedBraille[aOutputOrder], test.accOrElmOrID,
+ test.oldAccOrElmOrID, 0);
+ });
+ AccessFuTest.nextTest();
+ });
+ };
+ }
+
+ AccessFuTest.addFunc(testOutputOrder(0));
+ AccessFuTest.addFunc(testOutputOrder(1));
+ AccessFuTest.waitForExplicitFinish();
+ AccessFuTest.runTests();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+ <div id="root">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=830748"
+ title="[AccessFu] Improve reading of table semantics">
+ Mozilla Bug 830748
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ <table id="table1">
+ <thead>
+ <tr>
+ <th id="table1_th1">col1</th>
+ <th>col2</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td id="table1_td1">cell1</td>
+ <td id="table1_td2">cell2</td>
+ </tr>
+ </tbody>
+ </table>
+ <table id="table2" border="1">
+ <tr>
+ <td id="table2_cell_1" headers="table2_ch_1">cell1</td>
+ <td id="table2_cell_2" headers="table2_ch_2">
+ <table id="table3">
+ <thead>
+ <tr>
+ <th id="table3_ch">colheader</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td id="table3_cell">bla</td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ <tr>
+ <td id="table2_ch_1" scope="col">col1</td>
+ <td id="table2_ch_2" scope="col">col2</td>
+ </tr>
+ </table>
+ <table id="table4" border="1">
+ <thead>
+ <tr>
+ <th id="table4_ch_1">col1</th>
+ <th id="table4_ch_2">col2</th>
+ <td id="table4_ch_3" scope="col">col3</td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <th id="table4_rh_1" colspan="2">row1</th>
+ <td id="table4_cell_2">cell1</td>
+ <td id="table4_cell_3" rowspan="2">cell2</td>
+ </tr>
+ <tr>
+ <td id="table4_rh_2" scope="row">row2</td>
+ <td id="table4_cell_5">cell3</td>
+ <td id="table4_cell_6">cell4</td>
+ </tr>
+ </tbody>
+ </table>
+ <table id="table5">
+ <tr><td>Row1</td></tr>
+ <tr><td>Row2</td></tr>
+ </table>
+ </div>
+</body>
+</html> \ No newline at end of file
diff --git a/accessible/tests/mochitest/jsat/test_traversal.html b/accessible/tests/mochitest/jsat/test_traversal.html
new file mode 100644
index 000000000..533e9d89f
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/test_traversal.html
@@ -0,0 +1,167 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Tests AccessFu TraversalRules</title>
+ <meta charset="utf-8" />
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/chrome-harness.js">
+ </script>
+
+ <script type="application/javascript" src="../common.js"></script>
+ <script type="application/javascript" src="../browser.js"></script>
+ <script type="application/javascript" src="../events.js"></script>
+ <script type="application/javascript" src="../role.js"></script>
+ <script type="application/javascript" src="../states.js"></script>
+ <script type="application/javascript" src="../pivot.js"></script>
+ <script type="application/javascript" src="../layout.js"></script>
+
+ <script type="application/javascript">
+ Components.utils.import("resource://gre/modules/accessibility/Traversal.jsm");
+ var gBrowserWnd = null;
+ var gQueue = null;
+
+ function doTest()
+ {
+ var doc = currentTabDocument();
+ var docAcc = getAccessible(doc, [nsIAccessibleDocument]);
+
+ gQueue = new eventQueue();
+
+ gQueue.onFinish = function onFinish()
+ {
+ closeBrowserWindow();
+ }
+
+ queueTraversalSequence(gQueue, docAcc, TraversalRules.Heading, null,
+ ['heading-1', 'heading-2', 'heading-3', 'heading-5']);
+
+ queueTraversalSequence(gQueue, docAcc, TraversalRules.Entry, null,
+ ['input-1-1', 'label-1-2', 'input-1-3',
+ 'input-1-4', 'input-1-5']);
+
+ // move back an element to hit all the form elements, because the VC is
+ // currently at the first input element
+ gQueue.push(new setVCPosInvoker(docAcc, "movePrevious",
+ TraversalRules.Heading, "heading-1"));
+
+ queueTraversalSequence(gQueue, docAcc, TraversalRules.FormElement, null,
+ ['input-1-1', 'label-1-2', 'button-1-1',
+ 'radio-1-1', 'radio-1-2', 'input-1-3',
+ 'input-1-4', 'button-1-2', 'checkbox-1-1',
+ 'select-1-1', 'select-1-2', 'checkbox-1-2',
+ 'select-1-3', 'input-1-5', 'button-1-3',
+ 'button-2-1', 'button-2-2', 'button-2-3',
+ 'button-2-4', 'checkbox-1-5', 'switch-1']);
+
+ queueTraversalSequence(gQueue, docAcc, TraversalRules.Button, null,
+ ['button-1-1', 'button-1-2', 'button-1-3',
+ 'button-2-1', 'button-2-2', 'button-2-3',
+ 'button-2-4']);
+
+ queueTraversalSequence(gQueue, docAcc, TraversalRules.RadioButton, null,
+ ['radio-1-1', 'radio-1-2']);
+
+ queueTraversalSequence(gQueue, docAcc, TraversalRules.Checkbox, null,
+ ['checkbox-1-1', 'checkbox-1-2', 'checkbox-1-5',
+ 'switch-1']);
+
+ queueTraversalSequence(gQueue, docAcc, TraversalRules.Combobox, null,
+ ['select-1-1', 'select-1-2', 'select-1-3']);
+
+ queueTraversalSequence(gQueue, docAcc, TraversalRules.List, null,
+ ['list-1', 'list-2', 'list-3']);
+
+ queueTraversalSequence(gQueue, docAcc, TraversalRules.ListItem, null,
+ ['listitem-1-1', 'listitem-2-1', 'listitem-2-2',
+ 'listitem-3-1', 'listitem-3-2', 'listitem-3-3',
+ 'listitem-3-4', 'listitem-3-5', 'listitem-3-6',
+ 'listitem-2-3']);
+
+ queueTraversalSequence(gQueue, docAcc, TraversalRules.Graphic, null,
+ ['image-2', 'image-3']);
+
+ queueTraversalSequence(gQueue, docAcc, TraversalRules.Link, null,
+ ['link-0', 'link-1', 'link-2', 'link-3']);
+
+ queueTraversalSequence(gQueue, docAcc, TraversalRules.Anchor, null,
+ ['anchor-1', 'anchor-2']);
+
+ queueTraversalSequence(gQueue, docAcc, TraversalRules.Separator, null,
+ ['separator-1', 'separator-2']);
+
+ queueTraversalSequence(gQueue, docAcc, TraversalRules.Table, null,
+ ['table-1', 'grid', 'table-2']);
+
+ queueTraversalSequence(gQueue, docAcc, TraversalRules.Simple, null,
+ ['heading-1', 'Name:', 'input-1-1', 'label-1-2',
+ 'button-1-1', 'Radios are old: ', 'radio-1-1',
+ 'Radios are new: ', 'radio-1-2', 'Password:',
+ 'input-1-3', 'Unlucky number:', 'input-1-4',
+ 'button-1-2', 'Check me: ', 'checkbox-1-1',
+ 'select-1-1', 'Value 1', 'Value 2', 'Value 3',
+ 'Check me too: ', 'checkbox-1-2', 'But not me: ',
+ 'Or me! ', 'Value 1', 'Value 2', 'Value 3',
+ 'Electronic mailing address:', 'input-1-5',
+ 'button-1-3', 'heading-2', 'heading-3',
+ 'button-2-1', 'button-2-2', 'button-2-3',
+ 'button-2-4', 'Programming Language',
+ 'A esoteric weapon wielded by only the most ' +
+ 'formidable warriors, for its unrelenting strict' +
+ ' power is unfathomable.',
+ '• Lists of Programming Languages', 'Lisp ',
+ '1. Scheme', '2. Racket', '3. Clojure',
+ '4. Standard Lisp', 'link-0', ' Lisp',
+ 'checkbox-1-5', ' LeLisp', '• JavaScript',
+ 'heading-5', 'image-2', 'image-3',
+ 'Not actually an image', 'link-1', 'anchor-1',
+ 'link-2', 'anchor-2', 'link-3', '3', '1', '4',
+ '1', 'Sunday', 'M', 'Week 1', '3', '4', '7', '2',
+ '5 8', 'gridcell4', 'Just an innocuous separator',
+ 'Dirty Words', 'Meaning', 'Mud', 'Wet Dirt',
+ 'Dirt', 'Messy Stuff', 'statusbar-1', 'statusbar-2',
+ 'switch-1', 'This is a MathML formula ', 'math-1',
+ 'with some text after.']);
+
+ queueTraversalSequence(gQueue, docAcc, TraversalRules.Landmark, null,
+ ['header-1', 'main-1', 'footer-1']);
+
+
+ queueTraversalSequence(gQueue, docAcc, TraversalRules.Control, null,
+ ['input-1-1', 'label-1-2', 'button-1-1',
+ 'radio-1-1', 'radio-1-2', 'input-1-3',
+ 'input-1-4', 'button-1-2', 'checkbox-1-1',
+ 'select-1-1', 'select-1-2', 'checkbox-1-2',
+ 'select-1-3', 'input-1-5', 'button-1-3',
+ 'button-2-1', 'button-2-2', 'button-2-3',
+ 'button-2-4', 'link-0', 'checkbox-1-5',
+ 'link-1', 'link-2', 'link-3', 'switch-1']);
+
+ gQueue.invoke();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(function () {
+ /* We open a new browser because we need to test with a top-level content
+ document. */
+ openBrowserWindow(
+ doTest,
+ getRootDirectory(window.location.href) + "doc_traversal.html");
+ });
+ </script>
+</head>
+<body id="body">
+
+ <a target="_blank"
+ title="Add tests for AccessFu TraversalRules"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=933808">Mozilla Bug 933808</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/jsat/test_traversal_helper.html b/accessible/tests/mochitest/jsat/test_traversal_helper.html
new file mode 100644
index 000000000..f6a347ed0
--- /dev/null
+++ b/accessible/tests/mochitest/jsat/test_traversal_helper.html
@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Tests AccessFu TraversalRules</title>
+ <meta charset="utf-8" />
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/chrome-harness.js">
+ </script>
+
+ <script type="application/javascript" src="../common.js"></script>
+ <script type="application/javascript" src="../browser.js"></script>
+ <script type="application/javascript" src="../events.js"></script>
+ <script type="application/javascript" src="../role.js"></script>
+ <script type="application/javascript" src="../states.js"></script>
+ <script type="application/javascript" src="../pivot.js"></script>
+ <script type="application/javascript" src="../layout.js"></script>
+
+ <script type="application/javascript">
+ Components.utils.import("resource://gre/modules/accessibility/Traversal.jsm");
+
+ var vc;
+
+ function accessibleIs(aAccessible, aExpected, aMessage) {
+ if (!aAccessible && aAccessible == aExpected) {
+ ok(true, "Accessible is null. " + aMessage);
+ } else {
+ ok(aAccessible.DOMNode.id == aExpected || aAccessible.name == aExpected,
+ "expected '" + aExpected + "', got " + prettyName(vc.position) +
+ ". " + aMessage);
+ }
+ }
+
+ function walkSequence(aMethod, aRule, aExpectedSequence) {
+ for (var expected of aExpectedSequence) {
+ ok(TraversalHelper.move(vc, aMethod, aRule),
+ "successfully did " + aMethod + " with " + aRule);
+ accessibleIs(vc.position, expected, "landed on correct accessible");
+ }
+ }
+
+ function testTraversalHelper(aRule, aExpectedSequence) {
+ vc.position = null;
+
+ walkSequence('moveNext', aRule, aExpectedSequence);
+
+ ok(!TraversalHelper.move(vc, 'moveNext', aRule), "reached end");
+
+ TraversalHelper.move(vc, 'moveLast', 'Simple');
+
+ walkSequence('movePrevious', aRule,
+ Array.from(aExpectedSequence).reverse());
+
+ ok(!TraversalHelper.move(vc, 'movePrevious', aRule), "reached start");
+
+ vc.position = null;
+
+ ok(TraversalHelper.move(vc, 'moveFirst', aRule), "moveFirst");
+
+ accessibleIs(vc.position, aExpectedSequence[0],
+ "moveFirst to correct accessible");
+
+ ok(TraversalHelper.move(vc, 'moveLast', aRule), "moveLast");
+
+ accessibleIs(vc.position, aExpectedSequence[aExpectedSequence.length - 1],
+ "moveLast to correct accessible");
+ }
+
+
+ function doTest()
+ {
+ var doc = currentTabDocument();
+ var docAcc = getAccessible(doc, [nsIAccessibleDocument]);
+ vc = docAcc.virtualCursor;
+
+ testTraversalHelper('Landmark',
+ ['heading-1', 'heading-2', 'statusbar-1']);
+
+ testTraversalHelper('List',
+ ['Programming Language', 'listitem-2-1', 'listitem-3-1']);
+
+ testTraversalHelper('Section',
+ ['heading-1', 'heading-2', 'heading-3',
+ 'heading-5', 'link-1', 'statusbar-1']);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(function () {
+ /* We open a new browser because we need to test with a top-level content
+ document. */
+ openBrowserWindow(
+ doTest,
+ getRootDirectory(window.location.href) + "doc_traversal.html");
+ });
+ </script>
+</head>
+<body id="body">
+
+ <a target="_blank"
+ title="Add tests for AccessFu TraversalRules"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=xxx">Mozilla Bug xxx</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/layout.js b/accessible/tests/mochitest/layout.js
new file mode 100644
index 000000000..e1fb14670
--- /dev/null
+++ b/accessible/tests/mochitest/layout.js
@@ -0,0 +1,258 @@
+/**
+ * Tests if the given child and grand child accessibles at the given point are
+ * expected.
+ *
+ * @param aID [in] accessible identifier
+ * @param aX [in] x coordinate of the point relative accessible
+ * @param aY [in] y coordinate of the point relative accessible
+ * @param aChildID [in] expected child accessible
+ * @param aGrandChildID [in] expected child accessible
+ */
+function testChildAtPoint(aID, aX, aY, aChildID, aGrandChildID)
+{
+ var child = getChildAtPoint(aID, aX, aY, false);
+ var expectedChild = getAccessible(aChildID);
+
+ var msg = "Wrong direct child accessible at the point (" + aX + ", " + aY +
+ ") of " + prettyName(aID);
+ isObject(child, expectedChild, msg);
+
+ var grandChild = getChildAtPoint(aID, aX, aY, true);
+ var expectedGrandChild = getAccessible(aGrandChildID);
+
+ msg = "Wrong deepest child accessible at the point (" + aX + ", " + aY +
+ ") of " + prettyName(aID);
+ isObject(grandChild, expectedGrandChild, msg);
+}
+
+/**
+ * Test if getChildAtPoint returns the given child and grand child accessibles
+ * at coordinates of child accessible (direct and deep hit test).
+ */
+function hitTest(aContainerID, aChildID, aGrandChildID)
+{
+ var container = getAccessible(aContainerID);
+ var child = getAccessible(aChildID);
+ var grandChild = getAccessible(aGrandChildID);
+
+ var [x, y] = getBoundsForDOMElm(child);
+
+ var actualChild = container.getChildAtPoint(x + 1, y + 1);
+ isObject(actualChild, child,
+ "Wrong direct child of " + prettyName(aContainerID));
+
+ var actualGrandChild = container.getDeepestChildAtPoint(x + 1, y + 1);
+ isObject(actualGrandChild, grandChild,
+ "Wrong deepest child of " + prettyName(aContainerID));
+}
+
+/**
+ * Test if getOffsetAtPoint returns the given text offset at given coordinates.
+ */
+function testOffsetAtPoint(aHyperTextID, aX, aY, aCoordType, aExpectedOffset)
+{
+ var hyperText = getAccessible(aHyperTextID, [nsIAccessibleText]);
+ var offset = hyperText.getOffsetAtPoint(aX, aY, aCoordType);
+ is(offset, aExpectedOffset,
+ "Wrong offset at given point (" + aX + ", " + aY + ") for " +
+ prettyName(aHyperTextID));
+}
+
+/**
+ * Zoom the given document.
+ */
+function zoomDocument(aDocument, aZoom)
+{
+ var docShell = aDocument.defaultView.
+ QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIWebNavigation).
+ QueryInterface(Components.interfaces.nsIDocShell);
+ var docViewer = docShell.contentViewer;
+
+ docViewer.fullZoom = aZoom;
+}
+
+/**
+ * Return child accessible at the given point.
+ *
+ * @param aIdentifier [in] accessible identifier
+ * @param aX [in] x coordinate of the point relative accessible
+ * @param aY [in] y coordinate of the point relative accessible
+ * @param aFindDeepestChild [in] points whether deepest or nearest child should
+ * be returned
+ * @return the child accessible at the given point
+ */
+function getChildAtPoint(aIdentifier, aX, aY, aFindDeepestChild)
+{
+ var acc = getAccessible(aIdentifier);
+ if (!acc)
+ return;
+
+ var [screenX, screenY] = getBoundsForDOMElm(acc.DOMNode);
+
+ var x = screenX + aX;
+ var y = screenY + aY;
+
+ try {
+ if (aFindDeepestChild)
+ return acc.getDeepestChildAtPoint(x, y);
+ return acc.getChildAtPoint(x, y);
+ } catch (e) { }
+
+ return null;
+}
+
+/**
+ * Test the accessible position.
+ */
+function testPos(aID, aPoint)
+{
+ var [expectedX, expectedY] =
+ (aPoint != undefined) ? aPoint : getBoundsForDOMElm(aID);
+
+ var [x, y] = getBounds(aID);
+ is(x, expectedX, "Wrong x coordinate of " + prettyName(aID));
+ is(y, expectedY, "Wrong y coordinate of " + prettyName(aID));
+}
+
+/**
+ * Test the accessible boundaries.
+ */
+function testBounds(aID, aRect)
+{
+ var [expectedX, expectedY, expectedWidth, expectedHeight] =
+ (aRect != undefined) ? aRect : getBoundsForDOMElm(aID);
+
+ var [x, y, width, height] = getBounds(aID);
+ is(x, expectedX, "Wrong x coordinate of " + prettyName(aID));
+ is(y, expectedY, "Wrong y coordinate of " + prettyName(aID));
+ is(width, expectedWidth, "Wrong width of " + prettyName(aID));
+ is(height, expectedHeight, "Wrong height of " + prettyName(aID));
+}
+
+/**
+ * Test text position at the given offset.
+ */
+function testTextPos(aID, aOffset, aPoint, aCoordOrigin)
+{
+ var [expectedX, expectedY] = aPoint;
+
+ var xObj = {}, yObj = {};
+ var hyperText = getAccessible(aID, [nsIAccessibleText]);
+ hyperText.getCharacterExtents(aOffset, xObj, yObj, {}, {}, aCoordOrigin);
+ is(xObj.value, expectedX,
+ "Wrong x coordinate at offset " + aOffset + " for " + prettyName(aID));
+ ok(yObj.value - expectedY < 2 && expectedY - yObj.value < 2,
+ "Wrong y coordinate at offset " + aOffset + " for " + prettyName(aID) +
+ " - got " + yObj.value + ", expected " + expectedY +
+ "The difference doesn't exceed 1.");
+}
+
+/**
+ * Test text bounds that is enclosed betwene the given offsets.
+ */
+function testTextBounds(aID, aStartOffset, aEndOffset, aRect, aCoordOrigin)
+{
+ var [expectedX, expectedY, expectedWidth, expectedHeight] = aRect;
+
+ var xObj = {}, yObj = {}, widthObj = {}, heightObj = {};
+ var hyperText = getAccessible(aID, [nsIAccessibleText]);
+ hyperText.getRangeExtents(aStartOffset, aEndOffset,
+ xObj, yObj, widthObj, heightObj, aCoordOrigin);
+ is(xObj.value, expectedX,
+ "Wrong x coordinate of text between offsets (" + aStartOffset + ", " +
+ aEndOffset + ") for " + prettyName(aID));
+ is(yObj.value, expectedY,
+ "Wrong y coordinate of text between offsets (" + aStartOffset + ", " +
+ aEndOffset + ") for " + prettyName(aID));
+
+ var msg = "Wrong width of text between offsets (" + aStartOffset + ", " +
+ aEndOffset + ") for " + prettyName(aID);
+ if (widthObj.value == expectedWidth)
+ ok(true, msg);
+ else
+ todo(false, msg); // fails on some windows machines
+
+ is(heightObj.value, expectedHeight,
+ "Wrong height of text between offsets (" + aStartOffset + ", " +
+ aEndOffset + ") for " + prettyName(aID));
+}
+
+/**
+ * Return the accessible coordinates relative to the screen in device pixels.
+ */
+function getPos(aID)
+{
+ var accessible = getAccessible(aID);
+ var x = {}, y = {};
+ accessible.getBounds(x, y, {}, {});
+ return [x.value, y.value];
+}
+
+/**
+ * Return the accessible coordinates and size relative to the screen in device
+ * pixels.
+ */
+function getBounds(aID)
+{
+ var accessible = getAccessible(aID);
+ var x = {}, y = {}, width = {}, height = {};
+ accessible.getBounds(x, y, width, height);
+ return [x.value, y.value, width.value, height.value];
+}
+
+/**
+ * Return DOM node coordinates relative the screen and its size in device
+ * pixels.
+ */
+function getBoundsForDOMElm(aID)
+{
+ var x = 0, y = 0, width = 0, height = 0;
+
+ var elm = getNode(aID);
+ if (elm.localName == "area") {
+ var mapName = elm.parentNode.getAttribute("name");
+ var selector = "[usemap='#" + mapName + "']";
+ var img = elm.ownerDocument.querySelector(selector);
+
+ var areaCoords = elm.coords.split(",");
+ var areaX = parseInt(areaCoords[0]);
+ var areaY = parseInt(areaCoords[1]);
+ var areaWidth = parseInt(areaCoords[2]) - areaX;
+ var areaHeight = parseInt(areaCoords[3]) - areaY;
+
+ var rect = img.getBoundingClientRect();
+ x = rect.left + areaX;
+ y = rect.top + areaY;
+ width = areaWidth;
+ height = areaHeight;
+ }
+ else {
+ var rect = elm.getBoundingClientRect();
+ x = rect.left;
+ y = rect.top;
+ width = rect.width;
+ height = rect.height;
+ }
+
+ var elmWindow = elm.ownerDocument.defaultView;
+ return CSSToDevicePixels(elmWindow,
+ x + elmWindow.mozInnerScreenX,
+ y + elmWindow.mozInnerScreenY,
+ width,
+ height);
+}
+
+function CSSToDevicePixels(aWindow, aX, aY, aWidth, aHeight)
+{
+ var winUtil = aWindow.
+ QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIDOMWindowUtils);
+
+ var ratio = winUtil.screenPixelsPerCSSPixel;
+
+ // CSS pixels and ratio can be not integer. Device pixels are always integer.
+ // Do our best and hope it works.
+ return [ Math.round(aX * ratio), Math.round(aY * ratio),
+ Math.round(aWidth * ratio), Math.round(aHeight * ratio) ];
+}
diff --git a/accessible/tests/mochitest/letters.gif b/accessible/tests/mochitest/letters.gif
new file mode 100644
index 000000000..299b91784
--- /dev/null
+++ b/accessible/tests/mochitest/letters.gif
Binary files differ
diff --git a/accessible/tests/mochitest/longdesc_src.html b/accessible/tests/mochitest/longdesc_src.html
new file mode 100644
index 000000000..37248795d
--- /dev/null
+++ b/accessible/tests/mochitest/longdesc_src.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+<title>Mozilla logo</title>
+</head>
+<body>
+<p>This file would contain a longer description of the Mozilla logo, if I knew what it looked like.</p>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/moz.build b/accessible/tests/mochitest/moz.build
new file mode 100644
index 000000000..4fb277037
--- /dev/null
+++ b/accessible/tests/mochitest/moz.build
@@ -0,0 +1,37 @@
+# -*- 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/.
+
+A11Y_MANIFESTS += [
+ 'a11y.ini',
+ 'actions/a11y.ini',
+ 'aom/a11y.ini',
+ 'attributes/a11y.ini',
+ 'bounds/a11y.ini',
+ 'editabletext/a11y.ini',
+ 'elm/a11y.ini',
+ 'events/a11y.ini',
+ 'focus/a11y.ini',
+ 'hittest/a11y.ini',
+ 'hyperlink/a11y.ini',
+ 'hypertext/a11y.ini',
+ 'jsat/a11y.ini',
+ 'name/a11y.ini',
+ 'pivot/a11y.ini',
+ 'relations/a11y.ini',
+ 'role/a11y.ini',
+ 'scroll/a11y.ini',
+ 'selectable/a11y.ini',
+ 'states/a11y.ini',
+ 'table/a11y.ini',
+ 'text/a11y.ini',
+ 'textattrs/a11y.ini',
+ 'textcaret/a11y.ini',
+ 'textrange/a11y.ini',
+ 'textselection/a11y.ini',
+ 'tree/a11y.ini',
+ 'treeupdate/a11y.ini',
+ 'value/a11y.ini',
+]
diff --git a/accessible/tests/mochitest/moz.png b/accessible/tests/mochitest/moz.png
new file mode 100644
index 000000000..743292dc6
--- /dev/null
+++ b/accessible/tests/mochitest/moz.png
Binary files differ
diff --git a/accessible/tests/mochitest/name.js b/accessible/tests/mochitest/name.js
new file mode 100644
index 000000000..8d8290905
--- /dev/null
+++ b/accessible/tests/mochitest/name.js
@@ -0,0 +1,33 @@
+/**
+ * Test accessible name for the given accessible identifier.
+ */
+function testName(aAccOrElmOrID, aName, aMsg, aTodo)
+{
+ var msg = aMsg ? aMsg : "";
+
+ var acc = getAccessible(aAccOrElmOrID);
+ if (!acc)
+ return;
+
+ var func = aTodo ? todo_is : is;
+ var txtID = prettyName(aAccOrElmOrID);
+ try {
+ func(acc.name, aName, msg + "Wrong name of the accessible for " + txtID);
+ } catch (e) {
+ ok(false, msg + "Can't get name of the accessible for " + txtID);
+ }
+ return acc;
+}
+
+/**
+ * Test accessible description for the given accessible.
+ */
+function testDescr(aAccOrElmOrID, aDescr)
+{
+ var acc = getAccessible(aAccOrElmOrID);
+ if (!acc)
+ return;
+
+ is(acc.description, aDescr,
+ "Wrong description for " + prettyName(aAccOrElmOrID));
+}
diff --git a/accessible/tests/mochitest/name/a11y.ini b/accessible/tests/mochitest/name/a11y.ini
new file mode 100644
index 000000000..4d743f1f5
--- /dev/null
+++ b/accessible/tests/mochitest/name/a11y.ini
@@ -0,0 +1,20 @@
+[DEFAULT]
+support-files =
+ general.css
+ general.xbl
+ markup.js
+ markuprules.xml
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/moz.png
+
+[test_browserui.xul]
+[test_counterstyle.html]
+[test_general.html]
+[test_general.xul]
+[test_link.html]
+[test_list.html]
+[test_markup.html]
+skip-if = (debug && os == 'win') # Bug 1296784
+[test_svg.html]
+[test_toolbaritem.xul]
+[test_tree.xul]
diff --git a/accessible/tests/mochitest/name/general.css b/accessible/tests/mochitest/name/general.css
new file mode 100644
index 000000000..5f750c4dc
--- /dev/null
+++ b/accessible/tests/mochitest/name/general.css
@@ -0,0 +1,11 @@
+box.first {
+ -moz-binding: url('general.xbl#first');
+}
+
+.second {
+ -moz-binding: url('general.xbl#second');
+}
+
+.third {
+ -moz-binding: url('general.xbl#third');
+}
diff --git a/accessible/tests/mochitest/name/general.xbl b/accessible/tests/mochitest/name/general.xbl
new file mode 100644
index 000000000..07489f5f4
--- /dev/null
+++ b/accessible/tests/mochitest/name/general.xbl
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+
+<bindings xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="first">
+ <content>
+ <xul:textbox anonid="labeled" class="bottom"/>
+ <xul:label control="labeled" value="Label"/>
+ <children/>
+ </content>
+ </binding>
+
+ <binding id="second">
+ <content>
+ <xul:box class="first">
+ <xul:label control="toplabeled" value="Top textbox"/>
+ <xul:textbox anonid="toplabeled" class="top"/>
+ </xul:box>
+ <children/>
+ </content>
+ </binding>
+
+ <binding id="third">
+ <content>
+ <xul:description anonid="label" value="It's a " />
+ <xul:description anonid="label2" value="cool button" />
+ <xul:button anonid="button" aria-labelledby="label label2"
+ value="button" />
+ </content>
+ </binding>
+</bindings>
diff --git a/accessible/tests/mochitest/name/markup.js b/accessible/tests/mochitest/name/markup.js
new file mode 100644
index 000000000..d3ecd8982
--- /dev/null
+++ b/accessible/tests/mochitest/name/markup.js
@@ -0,0 +1,382 @@
+////////////////////////////////////////////////////////////////////////////////
+// Name tests described by "markuprules.xml" file.
+
+var gNameRulesFileURL = "markuprules.xml";
+
+var gRuleDoc = null;
+
+// Debuggin stuff.
+var gDumpToConsole = false;
+
+/**
+ * Start name tests. Run through markup elements and test names for test
+ * element (see namerules.xml for details).
+ */
+function testNames()
+{
+ //enableLogging("tree,stack"); // debugging
+
+ var request = new XMLHttpRequest();
+ request.open("get", gNameRulesFileURL, false);
+ request.send();
+
+ gRuleDoc = request.responseXML;
+
+ var markupElms = evaluateXPath(gRuleDoc, "//rules/rulesample/markup");
+ gTestIterator.iterateMarkups(markupElms);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Private section.
+
+/**
+ * Helper class to interate through name tests.
+ */
+var gTestIterator =
+{
+ iterateMarkups: function gTestIterator_iterateMarkups(aMarkupElms)
+ {
+ this.markupElms = aMarkupElms;
+
+ this.iterateNext();
+ },
+
+ iterateRules: function gTestIterator_iterateRules(aElm, aContainer,
+ aRuleSetElm, aRuleElms,
+ aTestID)
+ {
+ this.ruleSetElm = aRuleSetElm;
+ this.ruleElms = aRuleElms;
+ this.elm = aElm;
+ this.container = aContainer;
+ this.testID = aTestID;
+
+ this.iterateNext();
+ },
+
+ iterateNext: function gTestIterator_iterateNext()
+ {
+ if (this.markupIdx == -1) {
+ this.markupIdx++;
+ testNamesForMarkup(this.markupElms[this.markupIdx]);
+ return;
+ }
+
+ this.ruleIdx++;
+ if (this.ruleIdx == this.ruleElms.length) {
+ // When test is finished then name is empty and no explict-name.
+ var defaultName = this.ruleSetElm.hasAttribute("defaultName") ?
+ this.ruleSetElm.getAttribute("defaultName") : null;
+ testName(this.elm, defaultName,
+ "Default name test (" + gTestIterator.testID + "). ");
+ testAbsentAttrs(this.elm, {"explicit-name" : "true"});
+
+ this.markupIdx++;
+ if (this.markupIdx == this.markupElms.length) {
+ //disableLogging("tree"); // debugging
+ SimpleTest.finish();
+ return;
+ }
+
+ this.ruleIdx = -1;
+
+ if (gDumpToConsole) {
+ dump("\nPend next markup processing. Wait for reorder event on " +
+ prettyName(document) + "'\n");
+ }
+ waitForEvent(EVENT_REORDER, document, testNamesForMarkup,
+ null, this.markupElms[this.markupIdx]);
+
+ document.body.removeChild(this.container);
+ return;
+ }
+
+ testNameForRule(this.elm, this.ruleElms[this.ruleIdx]);
+ },
+
+ markupElms: null,
+ markupIdx: -1,
+ rulesetElm: null,
+ ruleElms: null,
+ ruleIdx: -1,
+ elm: null,
+ container: null,
+ testID: ""
+};
+
+/**
+ * Process every 'markup' element and test names for it. Used by testNames
+ * function.
+ */
+function testNamesForMarkup(aMarkupElm)
+{
+ if (gDumpToConsole)
+ dump("\nProcessing markup '" + aMarkupElm.getAttribute("id") + "'\n");
+
+ var div = document.createElement("div");
+ div.setAttribute("id", "test");
+
+ var child = aMarkupElm.firstChild;
+ while (child) {
+ var newChild = document.importNode(child, true);
+ div.appendChild(newChild);
+ child = child.nextSibling;
+ }
+
+ if (gDumpToConsole) {
+ dump("\nProcessing markup. Wait for reorder event on " +
+ prettyName(document) + "'\n");
+ }
+ waitForEvent(EVENT_REORDER, document, testNamesForMarkupRules,
+ null, aMarkupElm, div);
+
+ document.body.appendChild(div);
+}
+
+function testNamesForMarkupRules(aMarkupElm, aContainer)
+{
+ var testID = aMarkupElm.getAttribute("id");
+ if (gDumpToConsole)
+ dump("\nProcessing markup rules '" + testID + "'\n");
+
+ var serializer = new XMLSerializer();
+
+ var expr = "//html/body/div[@id='test']/" + aMarkupElm.getAttribute("ref");
+ var elm = evaluateXPath(document, expr, htmlDocResolver)[0];
+
+ var ruleId = aMarkupElm.getAttribute("ruleset");
+ var ruleElm = gRuleDoc.querySelector("[id='" + ruleId + "']");
+ var ruleElms = getRuleElmsByRulesetId(ruleId);
+
+ var processMarkupRules =
+ gTestIterator.iterateRules.bind(gTestIterator, elm, aContainer,
+ ruleElm, ruleElms, testID);
+
+ // Images may be recreated after we append them into subtree. We need to wait
+ // in this case. If we are on profiling enabled build then stack tracing
+ // works and thus let's log instead. Note, that works if you enabled logging
+ // (refer to testNames() function).
+ if (isAccessible(elm) || isLogged("stack"))
+ processMarkupRules();
+ else
+ waitForEvent(EVENT_SHOW, elm, processMarkupRules);
+}
+
+/**
+ * Test name for current rule and current 'markup' element. Used by
+ * testNamesForMarkup function.
+ */
+function testNameForRule(aElm, aRuleElm)
+{
+ if (aRuleElm.hasAttribute("attr")) {
+ if (gDumpToConsole) {
+ dump("\nProcessing rule { attr: " + aRuleElm.getAttribute("attr") +" }\n");
+ }
+
+ testNameForAttrRule(aElm, aRuleElm);
+
+ } else if (aRuleElm.hasAttribute("elm")) {
+ if (gDumpToConsole) {
+ dump("\nProcessing rule { elm: " + aRuleElm.getAttribute("elm") +
+ ", elmattr: " + aRuleElm.getAttribute("elmattr") +" }\n");
+ }
+
+ testNameForElmRule(aElm, aRuleElm);
+
+ } else if (aRuleElm.getAttribute("fromsubtree") == "true") {
+ if (gDumpToConsole) {
+ dump("\nProcessing rule { fromsubtree: " +
+ aRuleElm.getAttribute("fromsubtree") +" }\n");
+ }
+
+ testNameForSubtreeRule(aElm, aRuleElm);
+ }
+}
+
+function testNameForAttrRule(aElm, aRule)
+{
+ var name = "";
+
+ var attr = aRule.getAttribute("attr");
+ var attrValue = aElm.getAttribute(attr);
+
+ var type = aRule.getAttribute("type");
+ if (type == "string") {
+ name = attrValue;
+
+ } else if (type == "ref" && attrValue) {
+ var ids = attrValue.split(/\s+/);
+ for (var idx = 0; idx < ids.length; idx++) {
+ var labelElm = getNode(ids[idx]);
+ if (name != "")
+ name += " ";
+
+ name += labelElm.getAttribute("textequiv");
+ }
+ }
+
+ var msg = "Attribute '" + attr + "' test (" + gTestIterator.testID + "). ";
+ testName(aElm, name, msg);
+
+ if (aRule.getAttribute("explict-name") != "false")
+ testAttrs(aElm, {"explicit-name" : "true"}, true);
+ else
+ testAbsentAttrs(aElm, {"explicit-name" : "true"});
+
+ // If @recreated attribute is used then this attribute change recreates an
+ // accessible. Wait for reorder event in this case or otherwise proceed next
+ // test immediately.
+ if (aRule.hasAttribute("recreated")) {
+ waitForEvent(EVENT_REORDER, aElm.parentNode,
+ gTestIterator.iterateNext, gTestIterator);
+ aElm.removeAttribute(attr);
+
+ } else if (aRule.hasAttribute("textchanged")) {
+ waitForEvent(EVENT_TEXT_INSERTED, aElm,
+ gTestIterator.iterateNext, gTestIterator);
+ aElm.removeAttribute(attr);
+
+ } else if (aRule.hasAttribute("contentchanged")) {
+ waitForEvent(EVENT_REORDER, aElm,
+ gTestIterator.iterateNext, gTestIterator);
+ aElm.removeAttribute(attr);
+
+ } else {
+ aElm.removeAttribute(attr);
+ gTestIterator.iterateNext();
+ }
+}
+
+function testNameForElmRule(aElm, aRule)
+{
+ var labelElm;
+
+ var tagname = aRule.getAttribute("elm");
+ var attrname = aRule.getAttribute("elmattr");
+ if (attrname) {
+ var filter = {
+ acceptNode: function filter_acceptNode(aNode)
+ {
+ if (aNode.localName == this.mLocalName &&
+ aNode.getAttribute(this.mAttrName) == this.mAttrValue)
+ return NodeFilter.FILTER_ACCEPT;
+
+ return NodeFilter.FILTER_SKIP;
+ },
+
+ mLocalName: tagname,
+ mAttrName: attrname,
+ mAttrValue: aElm.getAttribute("id")
+ };
+
+ var treeWalker = document.createTreeWalker(document.body,
+ NodeFilter.SHOW_ELEMENT,
+ filter);
+ labelElm = treeWalker.nextNode();
+
+ } else {
+ // if attrname is empty then look for the element in subtree.
+ labelElm = aElm.getElementsByTagName(tagname)[0];
+ if (!labelElm)
+ labelElm = aElm.getElementsByTagName("html:" + tagname)[0];
+ }
+
+ if (!labelElm) {
+ ok(false, msg + " Failed to find '" + tagname + "' element.");
+ gTestIterator.iterateNext();
+ return;
+ }
+
+ var msg = "Element '" + tagname + "' test (" + gTestIterator.testID + ").";
+ testName(aElm, labelElm.getAttribute("textequiv"), msg);
+ testAttrs(aElm, {"explicit-name" : "true"}, true);
+
+ var parentNode = labelElm.parentNode;
+
+ if (gDumpToConsole) {
+ dump("\nProcessed elm rule. Wait for reorder event on " +
+ prettyName(parentNode) + "\n");
+ }
+ waitForEvent(EVENT_REORDER, parentNode,
+ gTestIterator.iterateNext, gTestIterator);
+
+ parentNode.removeChild(labelElm);
+}
+
+function testNameForSubtreeRule(aElm, aRule)
+{
+ var msg = "From subtree test (" + gTestIterator.testID + ").";
+ testName(aElm, aElm.getAttribute("textequiv"), msg);
+ testAbsentAttrs(aElm, {"explicit-name" : "true"});
+
+ if (gDumpToConsole) {
+ dump("\nProcessed from subtree rule. Wait for reorder event on " +
+ prettyName(aElm) + "\n");
+ }
+ waitForEvent(EVENT_REORDER, aElm, gTestIterator.iterateNext, gTestIterator);
+
+ while (aElm.firstChild)
+ aElm.removeChild(aElm.firstChild);
+}
+
+/**
+ * Return array of 'rule' elements. Used in conjunction with
+ * getRuleElmsFromRulesetElm() function.
+ */
+function getRuleElmsByRulesetId(aRulesetId)
+{
+ var expr = "//rules/ruledfn/ruleset[@id='" + aRulesetId + "']";
+ var rulesetElm = evaluateXPath(gRuleDoc, expr);
+ return getRuleElmsFromRulesetElm(rulesetElm[0]);
+}
+
+function getRuleElmsFromRulesetElm(aRulesetElm)
+{
+ var rulesetId = aRulesetElm.getAttribute("ref");
+ if (rulesetId)
+ return getRuleElmsByRulesetId(rulesetId);
+
+ var ruleElms = [];
+
+ var child = aRulesetElm.firstChild;
+ while (child) {
+ if (child.localName == "ruleset")
+ ruleElms = ruleElms.concat(getRuleElmsFromRulesetElm(child));
+ if (child.localName == "rule")
+ ruleElms.push(child);
+
+ child = child.nextSibling;
+ }
+
+ return ruleElms;
+}
+
+/**
+ * Helper method to evaluate xpath expression.
+ */
+function evaluateXPath(aNode, aExpr, aResolver)
+{
+ var xpe = new XPathEvaluator();
+
+ var resolver = aResolver;
+ if (!resolver) {
+ var node = aNode.ownerDocument == null ?
+ aNode.documentElement : aNode.ownerDocument.documentElement;
+ resolver = xpe.createNSResolver(node);
+ }
+
+ var result = xpe.evaluate(aExpr, aNode, resolver, 0, null);
+ var found = [];
+ var res;
+ while (res = result.iterateNext())
+ found.push(res);
+
+ return found;
+}
+
+function htmlDocResolver(aPrefix) {
+ var ns = {
+ 'html' : 'http://www.w3.org/1999/xhtml'
+ };
+ return ns[aPrefix] || null;
+}
diff --git a/accessible/tests/mochitest/name/markuprules.xml b/accessible/tests/mochitest/name/markuprules.xml
new file mode 100644
index 000000000..7f64ada34
--- /dev/null
+++ b/accessible/tests/mochitest/name/markuprules.xml
@@ -0,0 +1,373 @@
+<?xml version="1.0"?>
+
+<!--
+ This XML file is used to create sequence of accessible name tests. It consist
+ of two sections. The first section 'ruledfn' declares name computation rules.
+ The second section 'rulesample' defines markup samples we need to check name
+ computation rules for.
+
+ <ruledfn>
+ <ruleset>
+ <rule>
+
+ Section 'ruledfn' contains 'ruleset' elements. Every 'ruleset' element is
+ presented by 'rule' elements so that sequence of 'rule' elements gives the
+ sequence of name computations rules. Every 'rule' element can be one of four
+ types.
+
+ * <rule attr='' type='string'/> used when name is equal to the value of
+ attribute presented on the element.
+
+ Example, 'aria-label' attribute. In this case 'rule' element has 'attr'
+ attribute pointing to attribute name and 'type' attribute with 'string'
+ value. For example, <rule attr="aria-label" type="string"/>.
+
+ * <rule attr='' type='ref'/> used when name is calculated from elements that
+ are pointed to by attribute value on the element.
+
+ Example is 'aria-labelledby'. In this case 'rule' element has 'attr'
+ attribute holding the sequence of IDs of elements used to compute the name,
+ in addition the 'rule' element has 'type' attribute with 'ref' value.
+ For example, <rule attr="aria-labelledby" type="ref"/>.
+
+ * <rule elm='' elmattr=''/> used when name is calculated from another
+ element. These attributes are used to find an element by tagname and
+ attribute with value equaled to ID of the element. If 'elmattr' is missed
+ then element from subtree with the given tagname is used.
+
+ Example, html:label@for element, <rule elm="label" elmattr="for"/>.
+ Example, html:caption element, <rule elm="caption"/>
+
+ * <rule fromsubtree='true'/> used when name is computed from subtree.
+
+ Example, html:button. In this case 'rule' element has 'fromsubtree'
+ attribute with 'true' value.
+
+ <rulesample>
+ <markup ruleset=''>
+
+ Section 'rulesample' provides set of markup samples ('markup' elements). Every
+ 'markup' element contains an element that accessible name will be computed for
+ (let's call it test element). In addition the 'markup' element contains some
+ other elements from native markup used in name calculation process for test
+ element. Test element is pointed to by 'ref' attribute on 'markup' element.
+ Also 'markup' element has 'ruleset' attribute to indicate ruleset for the test
+ element.
+
+ How does it work? Let's consider simple example:
+ <ruledfn>
+ <ruleset id="aria">
+ <rule attr="aria-label" type="string"/>
+ <rule attr="aria-labelledby" type="ref"/>
+ </ruleset>
+ </ruledfn>
+ <rulesample>
+ <markup ref="html:div" ruleset="aria">
+ <html:span id="label" textequiv="test2">test2</html:span>
+ <html:div aria-label="test1"
+ aria-labelledby="label">it's a div</html:div>
+ </markup>
+ </rulesample>
+
+ Initially 'markup' element holds markup for all rules specified by 'ruleset'
+ attribute. This allows us to check if the sequence of name computation rules
+ is correct. Here 'ruleset' element defines two rules. We get the first rule
+ which means accesible name is computed from value of 'aria-label' attribute.
+ Then we check accessible name for the test element and remove 'aria-label'
+ attribute. After we get the second rule which means we should get IDs from
+ 'aria-labelledby' attribute and compose accessible name from values of
+ 'textequiv' attributes (that are supposed to give the desired name for each
+ element that is being pointed to by aria-labelledby). Check accessible name
+ and finish test.
+-->
+
+<rules xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <ruledfn>
+
+ <!-- bricks -->
+ <ruleset id="ARIA">
+ <rule attr="aria-labelledby" type="ref"/>
+ <rule attr="aria-label" type="string"/>
+ </ruleset>
+
+ <ruleset id="HTMLControl:Head">
+ <ruleset ref="ARIA"/>
+ <rule elm="label" elmattr="for"/>
+ </ruleset>
+
+ <!-- general -->
+ <ruleset id="HTMLControl">
+ <ruleset ref="HTMLControl:Head"/>
+ <rule fromsubtree="true"/>
+ <rule attr="title" type="string"/>
+ </ruleset>
+
+ <ruleset id="HTMLElm">
+ <ruleset ref="ARIA"/>
+ <rule attr="title" type="string"/>
+ </ruleset>
+
+ <!-- specific -->
+ <ruleset id="HTMLARIAGridCell">
+ <ruleset ref="ARIA"/>
+ <rule fromsubtree="true"/>
+ <rule attr="title" type="string"/>
+ </ruleset>
+
+ <ruleset id="HTMLInputButton">
+ <ruleset ref="HTMLControl:Head"/>
+ <rule attr="value" type="string" explict-name="false" reordered="true"/>
+ <rule attr="title" type="string"/>
+ </ruleset>
+
+ <ruleset id="HTMLInputSubmit" defaultName="Submit Query">
+ <ruleset ref="HTMLControl:Head"/>
+ <rule attr="value" type="string" explict-name="false" textchanged="true"/>
+ </ruleset>
+
+ <ruleset id="HTMLInputReset" defaultName="Reset">
+ <ruleset ref="HTMLControl:Head"/>
+ <rule attr="value" type="string" explict-name="false" textchanged="true"/>
+ </ruleset>
+
+ <ruleset id="HTMLInputImage">
+ <ruleset ref="HTMLControl:Head"/>
+ <rule attr="alt" type="string" recreated="true"/>
+ <rule attr="value" type="string" recreated="true"/>
+ <rule attr="title" type="string"/>
+ </ruleset>
+
+ <ruleset id="HTMLInputImageNoValidSrc" defaultName="Submit Query">
+ <ruleset ref="HTMLControl:Head"/>
+ <rule attr="alt" type="string" explict-name="false" recreated="true"/>
+ <rule attr="value" type="string" explict-name="false" recreated="true"/>
+ </ruleset>
+
+ <ruleset id="HTMLOption">
+ <ruleset ref="ARIA"/>
+ <rule attr="label" type="string"/>
+ <rule fromsubtree="true"/>
+ <rule attr="title" type="string"/>
+ </ruleset>
+
+ <ruleset id="HTMLImg">
+ <ruleset ref="ARIA"/>
+ <rule attr="alt" type="string"/>
+ <rule attr="title" type="string"/>
+ </ruleset>
+
+ <ruleset id="HTMLImgEmptyAlt">
+ <ruleset ref="ARIA"/>
+ <rule attr="title" type="string"/>
+ <rule attr="alt" type="string"/>
+ </ruleset>
+
+ <ruleset id="HTMLTable">
+ <ruleset ref="ARIA"/>
+ <rule elm="caption"/>
+ <rule attr="summary" type="string"/>
+ <rule attr="title" type="string"/>
+ </ruleset>
+ </ruledfn>
+
+ <rulesample>
+
+ <markup id="HTMLButtonTest"
+ ref="html:button" ruleset="HTMLControl">
+ <html:span id="l1" textequiv="test2">test2</html:span>
+ <html:span id="l2" textequiv="test3">test3</html:span>
+ <html:label for="btn" textequiv="test4">test4</html:label>
+ <html:button id="btn"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ title="test5"
+ textequiv="press me">press me</html:button>
+ </markup>
+
+ <markup id="HTMLInputButtonTest"
+ ref="html:input" ruleset="HTMLInputButton">
+ <html:span id="l1" textequiv="test2">test2</html:span>
+ <html:span id="l2" textequiv="test3">test3</html:span>
+ <html:label for="btn" textequiv="test4">test4</html:label>
+ <html:input id="btn"
+ type="button"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ value="name from value"
+ alt="no name from al"
+ src="no name from src"
+ data="no name from data"
+ title="name from title"/>
+ </markup>
+
+ <markup id="HTMLInputSubmitTest"
+ ref="html:input" ruleset="HTMLInputSubmit">
+ <html:span id="l1" textequiv="test2">test2</html:span>
+ <html:span id="l2" textequiv="test3">test3</html:span>
+ <html:label for="btn-submit" textequiv="test4">test4</html:label>
+ <html:input id="btn-submit"
+ type="submit"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ value="name from value"
+ alt="no name from atl"
+ src="no name from src"
+ data="no name from data"
+ title="no name from title"/>
+ </markup>
+
+ <markup id="HTMLInputResetTest"
+ ref="html:input" ruleset="HTMLInputReset">
+ <html:span id="l1" textequiv="test2">test2</html:span>
+ <html:span id="l2" textequiv="test3">test3</html:span>
+ <html:label for="btn-reset" textequiv="test4">test4</html:label>
+ <html:input id="btn-reset"
+ type="reset"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ value="name from value"
+ alt="no name from alt"
+ src="no name from src"
+ data="no name from data"
+ title="no name from title"/>
+ </markup>
+
+ <markup id="HTMLInputImageTest"
+ ref="html:input" ruleset="HTMLInputImage">
+ <html:span id="l1" textequiv="test2">test2</html:span>
+ <html:span id="l2" textequiv="test3">test3</html:span>
+ <html:label for="btn-image" textequiv="test4">test4</html:label>
+ <html:input id="btn-image"
+ type="image"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ alt="name from alt"
+ value="name from value"
+ src="../moz.png"
+ data="no name from data"
+ title="name from title"/>
+ </markup>
+
+ <markup id="HTMLInputImageNoValidSrcTest"
+ ref="html:input" ruleset="HTMLInputImageNoValidSrc">
+ <html:span id="l1" textequiv="test2">test2</html:span>
+ <html:span id="l2" textequiv="test3">test3</html:span>
+ <html:label for="btn-image" textequiv="test4">test4</html:label>
+ <html:input id="btn-image"
+ type="image"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ alt="name from alt"
+ value="name from value"
+ data="no name from data"
+ title="no name from title"/>
+ </markup>
+
+ <markup id="HTMLOptionTest"
+ ref="html:select/html:option[1]" ruleset="HTMLOption">
+ <html:span id="l1" textequiv="test2">test2</html:span>
+ <html:span id="l2" textequiv="test3">test3</html:span>
+ <html:select>
+ <html:option id="opt"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ label="test4"
+ title="test5"
+ textequiv="option1">option1</html:option>
+ <html:option>option2</html:option>
+ </html:select>
+ </markup>
+
+ <markup id="HTMLImageTest"
+ ref="html:img" ruleset="HTMLImg">
+ <html:span id="l1" textequiv="test2">test2</html:span>
+ <html:span id="l2" textequiv="test3">test3</html:span>
+ <html:img id="img"
+ aria-label="Logo of Mozilla"
+ aria-labelledby="l1 l2"
+ alt="Mozilla logo"
+ title="This is a logo"
+ src="../moz.png"/>
+ </markup>
+
+ <markup id="HTMLImageEmptyAltTest"
+ ref="html:img" ruleset="HTMLImgEmptyAlt">
+ <html:span id="l1" textequiv="test2">test2</html:span>
+ <html:span id="l2" textequiv="test3">test3</html:span>
+ <html:img id="imgemptyalt"
+ aria-label="Logo of Mozilla"
+ aria-labelledby="l1 l2"
+ title="This is a logo"
+ alt=""
+ src="../moz.png"/>
+ </markup>
+
+ <markup id="HTMLTdTest"
+ ref="html:table/html:tr/html:td" ruleset="HTMLElm">
+ <html:span id="l1" textequiv="test2">test2</html:span>
+ <html:span id="l2" textequiv="test3">test3</html:span>
+ <html:label for="tc" textequiv="test4">test4</html:label>
+ <html:table>
+ <html:tr>
+ <html:td id="tc"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ title="test5">
+ <html:p>This is a paragraph</html:p>
+ <html:a href="#">This is a link</html:a>
+ <html:ul>
+ <html:li>This is a list</html:li>
+ </html:ul>
+ </html:td>
+ </html:tr>
+ </html:table>
+ </markup>
+
+ <markup id="HTMLTdARIAGridCellTest"
+ ref="html:table/html:tr/html:td" ruleset="HTMLARIAGridCell">
+ <html:span id="l1" textequiv="test2">test2</html:span>
+ <html:span id="l2" textequiv="test3">test3</html:span>
+ <html:label for="gc" textequiv="test4">test4</html:label>
+ <html:table>
+ <html:tr>
+ <html:td id="gc"
+ role="gridcell"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ textequiv="This is a paragraph This is a link • Listitem1 • Listitem2"
+ title="This is a paragraph This is a link This is a list">
+ <html:p>This is a paragraph</html:p>
+ <html:a href="#">This is a link</html:a>
+ <html:ul>
+ <html:li>Listitem1</html:li>
+ <html:li>Listitem2</html:li>
+ </html:ul>
+ </html:td>
+ </html:tr>
+ </html:table>
+ </markup>
+
+ <markup id="HTMLTableTest"
+ ref="html:table" ruleset="HTMLTable">
+ <html:span id="l1" textequiv="lby_tst6_1">lby_tst6_1</html:span>
+ <html:span id="l2" textequiv="lby_tst6_2">lby_tst6_2</html:span>
+ <html:label for="t" textequiv="label_tst6">label_tst6</html:label>
+ <!-- layout frame are recreated due to varous reasons, here's text frame
+ placed after caption frame triggres table frame recreation when
+ caption element is removed from DOM; get rid text node after caption
+ node to make the test working -->
+ <html:table id="t" aria-label="arialabel_tst6"
+ aria-labelledby="l1 l2"
+ summary="summary_tst6"
+ title="title_tst6">
+ <html:caption textequiv="caption_tst6">caption_tst6</html:caption><html:tr>
+ <html:td>cell1</html:td>
+ <html:td>cell2</html:td>
+ </html:tr>
+ </html:table>
+ </markup>
+
+ </rulesample>
+</rules>
diff --git a/accessible/tests/mochitest/name/test_browserui.xul b/accessible/tests/mochitest/name/test_browserui.xul
new file mode 100644
index 000000000..ec21708fd
--- /dev/null
+++ b/accessible/tests/mochitest/name/test_browserui.xul
@@ -0,0 +1,107 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessibility Name Calculating Test.">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../browser.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ function addTab(aURL)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 1)
+ ];
+
+ this.invoke = function addTab_invoke()
+ {
+ tabBrowser().addTab(aURL);
+ }
+
+ this.getID = function addTab_getID()
+ {
+ return "add tab: " + aURL;
+ }
+ }
+
+ function switchTab(aTabBrowser, aWindow)
+ {
+ this.invoke = function switchTab_invoke()
+ {
+ synthesizeKey("VK_TAB", { ctrlKey: true }, browserWindow());
+ }
+
+ this.eventSeq = [
+ new focusChecker(tabDocumentAt, 1)
+ ];
+
+ this.check = function switchTab_check(aEvent)
+ {
+ var title = getAccessible(browserDocument()).name;
+ isnot(title.indexOf(aEvent.accessible.name), -1,
+ "Window title contains the name of active tab document" +
+ " (Is '" + aEvent.accessible.name + "' in '" + title + "'?)");
+ }
+
+ this.getID = function switchTab_getID() { return "switch tab"; }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Tests
+
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true; // debug
+
+ var gQueue = null;
+ function doTests()
+ {
+ gQueue = new eventQueue();
+ gQueue.push(new addTab("about:mozilla"));
+ gQueue.push(new switchTab());
+ gQueue.onFinish = function()
+ {
+ closeBrowserWindow();
+ }
+
+ gQueue.invoke();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ openBrowserWindow(doTests, "about:");
+ ]]>
+ </script>
+
+ <vbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=507382"
+ title="focus is fired earlier than root accessible name is changed when switching between tabs">
+ Mozilla Bug
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="eventdump"></vbox>
+ </vbox>
+</window>
diff --git a/accessible/tests/mochitest/name/test_counterstyle.html b/accessible/tests/mochitest/name/test_counterstyle.html
new file mode 100644
index 000000000..506cea69a
--- /dev/null
+++ b/accessible/tests/mochitest/name/test_counterstyle.html
@@ -0,0 +1,153 @@
+<html>
+
+<head>
+ <title>nsIAccessible::name calculation for @counter-style</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+
+ <style id="counterstyles" type="text/css">
+ @counter-style system-alphabetic {
+ system: alphabetic;
+ symbols: x y z;
+ }
+ @counter-style system-cyclic {
+ system: cyclic;
+ symbols: x y z;
+ }
+ @counter-style system-numeric {
+ system: numeric;
+ symbols: x y z;
+ }
+ @counter-style speak-as-bullets {
+ system: extends decimal;
+ speak-as: bullets;
+ }
+ @counter-style speak-as-numbers {
+ system: extends system-alphabetic;
+ speak-as: numbers;
+ }
+ @counter-style speak-as-words {
+ system: additive;
+ additive-symbols: 20 "twenty ", 9 "nine", 7 "seven", 1 "one";
+ speak-as: words;
+ }
+ @counter-style speak-as-spell-out {
+ system: extends system-alphabetic;
+ speak-as: spell-out;
+ }
+ @counter-style speak-as-other {
+ system: extends decimal;
+ speak-as: speak-as-words;
+ }
+ @counter-style speak-as-loop {
+ system: extends upper-latin;
+ speak-as: speak-as-loop0;
+ }
+ @counter-style speak-as-loop0 {
+ system: extends disc;
+ speak-as: speak-as-loop1;
+ }
+ @counter-style speak-as-loop1 {
+ system: extends decimal;
+ speak-as: speak-as-loop0;
+ }
+ @counter-style speak-as-extended0 {
+ system: extends decimal;
+ speak-as: speak-as-extended1;
+ }
+ @counter-style speak-as-extended1 {
+ system: extends speak-as-extended0;
+ speak-as: disc;
+ }
+ @counter-style speak-as-extended2 {
+ system: extends decimal;
+ speak-as: speak-as-extended3;
+ }
+ @counter-style speak-as-extended3 {
+ system: extends speak-as-extended2;
+ }
+ </style>
+
+ <script type="application/javascript">
+
+ function doTest()
+ {
+ function testRule(aRule, aNames, aTodo)
+ {
+ testName(aRule + "-1", aNames[0], null, aTodo);
+ testName(aRule + "-7", aNames[1], null, aTodo);
+ testName(aRule + "-29", aNames[2], null, aTodo);
+ }
+
+ var spellOutNames = ["X. 1", "Y X. 7", "Y Z Y. 29"];
+ var bulletsNames = [kDiscBulletText + "1",
+ kDiscBulletText + "7",
+ kDiscBulletText + "29"];
+ var numbersNames = ["1. 1", "7. 7", "29. 29"];
+ var wordsNames = ["one. 1", "seven. 7", "twenty nine. 29"];
+
+ testRule("system-alphabetic", spellOutNames, true); // bug 1024178
+ testRule("system-cyclic", bulletsNames);
+ testRule("system-numeric", numbersNames);
+
+ testRule("speak-as-bullets", bulletsNames);
+ testRule("speak-as-numbers", numbersNames);
+ testRule("speak-as-words", wordsNames);
+ testRule("speak-as-spell-out", spellOutNames, true); // bug 1024178
+ testRule("speak-as-other", wordsNames);
+
+ testRule("speak-as-loop", bulletsNames);
+ testRule("speak-as-loop0", bulletsNames);
+ testRule("speak-as-loop1", numbersNames);
+
+ testRule("speak-as-extended0", bulletsNames);
+ testRule("speak-as-extended1", bulletsNames);
+ testRule("speak-as-extended2", numbersNames);
+ testRule("speak-as-extended3", numbersNames);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=966166"
+ title="Bug 966166 - Implement @counter-style rule">
+ Bug 966166
+ </a>
+
+ <ol id="list"></ol>
+
+ <script type="application/javascript">
+ var list = getNode("list");
+ var rules = getNode("counterstyles").sheet.cssRules;
+ var values = [1, 7, 29];
+ for (var i = 0; i < rules.length; i++) {
+ var rule = rules[i];
+ for (var j = 0; j < values.length; j++) {
+ var item = document.createElement("li");
+ item.id = rule.name + '-' + values[j];
+ item.value = values[j];
+ item.textContent = values[j];
+ item.setAttribute("style", "list-style-type: " + rule.name);
+ list.appendChild(item);
+ }
+ }
+ </script>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/name/test_general.html b/accessible/tests/mochitest/name/test_general.html
new file mode 100644
index 000000000..28e7a11a9
--- /dev/null
+++ b/accessible/tests/mochitest/name/test_general.html
@@ -0,0 +1,631 @@
+<html>
+
+<head>
+ <title>nsIAccessible::name calculation</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest()
+ {
+ // aria-label
+
+ // Simple label provided via ARIA
+ testName("btn_simple_aria_label", "I am a button");
+
+ // aria-label and aria-labelledby, expect aria-labelledby
+ testName("btn_both_aria_labels", "text I am a button, two");
+
+ //////////////////////////////////////////////////////////////////////////
+ // aria-labelledby
+
+ // Single relation. The value of 'aria-labelledby' contains the ID of
+ // an element. Gets the name from text node of that element.
+ testName("btn_labelledby_text", "text");
+
+ // Multiple relations. The value of 'aria-labelledby' contains the IDs
+ // of elements. Gets the name from text nodes of those elements.
+ testName("btn_labelledby_texts", "text1 text2");
+
+ //////////////////////////////////////////////////////////////////////////
+ // Name from named accessible
+
+ testName("input_labelledby_namedacc", "Data");
+
+ //////////////////////////////////////////////////////////////////////////
+ // Name from subtree (single relation labelled_by).
+
+ // Gets the name from text nodes contained by nested elements
+ testName("btn_labelledby_mixed", "nomore text");
+
+ // Gets the name from text nodes contained by nested elements, ignores
+ // hidden elements (bug 443081).
+ testName("btn_labelledby_mixed_hidden_child", "nomore text2");
+
+ // Gets the name from hidden text nodes contained by nested elements,
+ // (label element is hidden entirely), (bug 443081).
+ testName("btn_labelledby_mixed_hidden", "lala more hidden text");
+
+ // Gets the name from text nodes contained by nested elements having block
+ // representation (every text node value in the name should be devided by
+ // spaces)
+ testName("btn_labelledby_mixed_block", "text more text");
+
+ // Gets the name from text nodes contained by html:td (every text node
+ // value in the name should be devided by spaces).
+ // XXX: this case is rather a feature than strong wanted behaviour.
+ testName("btn_labelledby_mixed_table", "text space text");
+
+ // Gets the name from image accessible.
+ testName("btn_labelledby_mixed_img", "text image");
+
+ // Gets the name from input accessibles
+ // Note: if input have label elements then the name isn't calculated
+ // from them.
+ testName("btn_labelledby_mixed_input",
+ "input button Submit Query Reset Submit Query");
+
+ // Gets the name from the title of object element.
+ testName("btn_labelledby_mixed_object", "object");
+
+ // Gets the name from text nodes. Element br adds space between them.
+ testName("btn_labelledby_mixed_br", "text text");
+
+ // Gets the name from label content which allows name from subtree,
+ // ignore @title attribute on label
+ testName("from_label_ignoretitle", "Country:");
+
+ // Gets the name from html:p content, which doesn't allow name from
+ // subtree, ignore @title attribute on label
+ testName("from_p_ignoretitle", "Choose country from.");
+
+ // Gets the name from html:input value, ignore @title attribute on input
+ testName("from_input_ignoretitle", "Custom country");
+
+ // Insert spaces around the control's value to not jamm sibling text nodes
+ testName("insert_spaces_around_control", "start value end");
+
+ // Gets the name from @title, ignore whitespace content
+ testName("from_label_ignore_ws_subtree", "about");
+
+ //////////////////////////////////////////////////////////////////////////
+ // label element
+
+ // The label element contains the button. The name is calculated from
+ // this button.
+ // Note: the name contains the content of the button.
+ testName("btn_label_inside", "text10text");
+
+ // The label element and the button are placed in the same form. Gets
+ // the name from the label subtree.
+ testName("btn_label_inform", "in form");
+
+ // The label element is placed outside of form where the button is.
+ // Take into account the label.
+ testName("btn_label_outform", "out form");
+
+ // The label element and the button are in the same document. Gets the
+ // name from the label subtree.
+ testName("btn_label_indocument", "in document");
+
+ // Multiple label elements for single button
+ testName("btn_label_multi", "label1label2");
+
+ // Multiple controls inside a label element
+ testName("ctrl_in_label_1", "Enable a button control");
+ testName("ctrl_in_label_2", "button");
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // name from children
+
+ // ARIA role button is presented allowing the name calculation from
+ // children.
+ testName("btn_children", "14");
+
+ // html:button, no name from content
+ testName("btn_nonamefromcontent", null);
+
+ // ARIA role option is presented allowing the name calculation from
+ // visible children (bug 443081).
+ testName("lb_opt1_children_hidden", "i am visible");
+
+ // Get the name from subtree of menuitem crossing role nothing to get
+ // the name from its children.
+ testName("tablemenuitem", "menuitem 1");
+
+ // Get the name from child acronym title attribute rather than from
+ // acronym content.
+ testName("label_with_acronym", "O A T F World Wide Web");
+
+ testName("testArticle", "Test article");
+
+ //////////////////////////////////////////////////////////////////////////
+ // title attribute
+
+ // If nothing is left. Let's try title attribute.
+ testName("btn_title", "title");
+
+ //////////////////////////////////////////////////////////////////////////
+ // textarea name
+
+ // textarea's name should have the value, which initially is specified by
+ // a text child.
+ testName("textareawithchild", "Story Foo is ended.");
+
+ // new textarea name should reflect the value change.
+ var elem = document.getElementById("textareawithchild");
+ elem.value = "Bar";
+
+ testName("textareawithchild", "Story Bar is ended.");
+
+ //////////////////////////////////////////////////////////////////////////
+ // controls having a value used as a part of computed name
+
+ testName("ctrlvalue_progressbar:input", "foo 5 baz");
+ testName("ctrlvalue_scrollbar:input", "foo 5 baz");
+ testName("ctrlvalue_slider:input", "foo 5 baz");
+ testName("ctrlvalue_spinbutton:input", "foo 5 baz");
+ testName("ctrlvalue_combobox:input", "foo 5 baz");
+
+
+ /////////////////////////////////////////////////////////////////////////
+ // label with nested combobox (test for 'f' item of name computation guide)
+
+ testName("comboinstart", "One day(s).");
+ testName("combo3", "day(s).");
+
+ testName("textboxinstart", "Two days.");
+ testName("textbox1", "days.");
+
+ testName("comboinmiddle", "Subscribe to ATOM feed.");
+ testName("combo4", "Subscribe to ATOM feed.");
+
+ testName("comboinmiddle2", "Play the Haliluya sound when new mail arrives");
+ testName("combo5", null); // label isn't used as a name for control
+ testName("checkbox", "Play the Haliluya sound when new mail arrives");
+ testName("comboinmiddle3", "Play the Haliluya sound when new mail arrives");
+ testName("combo6", "Play the Haliluya sound when new mail arrives");
+
+ testName("comboinend", "This day was sunny");
+ testName("combo7", "This day was");
+
+ testName("textboxinend", "This day was sunny");
+ testName("textbox2", "This day was");
+
+ // placeholder
+ testName("ph_password", "a placeholder");
+ testName("ph_text", "a placeholder");
+ testName("ph_textarea", "a placeholder");
+ testName("ph_text2", "a label");
+ testName("ph_textarea2", "a label");
+ testName("ph_text3", "a label");
+
+ // Test equation image
+ testName("img_eq", "x^2 + y^2 + z^2")
+ testName("input_img_eq", "x^2 + y^2 + z^2")
+ testName("txt_eq", "x^2 + y^2 + z^2")
+
+ ////////////////////////////////////////////////////////////////////////
+ // tests for duplicate announcement of content
+
+ testName("test_note", null);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=428479"
+ title="Bug 428479 - Support ARIA role=math">
+ Bug 428479
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=429666"
+ title="Expose ROLE_DOCUMENT for ARIA landmarks that inherit from document">
+ Bug 429666
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=444279"
+ title="mochitest for accessible name calculating">
+ Bug 444279
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=459635"
+ title="nsIAccessible::name calculation for HTML buttons">
+ Bug 459635
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=530081"
+ title="Clean up our tree walker">
+ Bug 530081
+ </a><br>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=604391"
+ title="Use placeholder as name if name is otherwise empty">
+ Bug 604391
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=669312"
+ title="Accessible name is duplicated when input has a label associated uisng for/id and is wrapped around the input">
+ Bug 669312
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=704416"
+ title="HTML acronym and abbr names should be provided by @title">
+ Bug 704416
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=812041"
+ title="ARIA slider and spinbutton don't provide a value for name computation">
+ Bug 812041
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=823927"
+ title="Text is jammed with control's text in name computation">
+ Bug 823927
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=835666"
+ title="ARIA combobox selected value is not a part of name computation">
+ Bug 835666
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=833256"
+ title="role note shouldn't pick up the name from subtree">
+ Mozilla Bug 833256
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- aria-label, simple label -->
+ <span id="btn_simple_aria_label" role="button" aria-label="I am a button"/>
+ <br/>
+ <!-- aria-label plus aria-labelledby -->
+ <span id="btn_both_aria_labels" role="button" aria-label="I am a button, two"
+ aria-labelledby="labelledby_text btn_both_aria_labels"/>
+ <br/>
+
+ <!-- aria-labelledby, single relation -->
+ <span id="labelledby_text">text</span>
+ <button id="btn_labelledby_text"
+ aria-labelledby="labelledby_text">1</button>
+ <br/>
+
+ <!-- aria-labelledby, multiple relations -->
+ <span id="labelledby_text1">text1</span>
+ <span id="labelledby_text2">text2</span>
+ <button id="btn_labelledby_texts"
+ aria-labelledby="labelledby_text1 labelledby_text2">2</button>
+ <br/>
+
+ <!-- name from named accessible -->
+ <input id="labelledby_namedacc" type="checkbox"
+ aria-label="Data" />
+ <input id="input_labelledby_namedacc"
+ aria-labelledby="labelledby_namedacc" />
+
+ <!-- the name from subtree, mixed content -->
+ <span id="labelledby_mixed">no<span>more text</span></span>
+ <button id="btn_labelledby_mixed"
+ aria-labelledby="labelledby_mixed">3</button>
+ <br/>
+
+ <!-- the name from subtree, mixed/hidden content -->
+ <span id="labelledby_mixed_hidden_child">
+ no<span>more
+ <span style="display: none;">hidden</span>
+ text2
+ <span style="visibility: hidden">hidden2</span>
+ </span>
+ </span>
+ <button id="btn_labelledby_mixed_hidden_child"
+ aria-labelledby="labelledby_mixed_hidden_child">3.1</button>
+ <br/>
+
+ <!-- the name from subtree, mixed/completely hidden content -->
+ <span id="labelledby_mixed_hidden"
+ style="display: none;">lala <span>more hidden </span>text</span></span>
+ <button id="btn_labelledby_mixed_hidden"
+ aria-labelledby="labelledby_mixed_hidden">3.2</button>
+ <br/>
+
+ <!-- the name from subtree, mixed content, block structure -->
+ <div id="labelledby_mixed_block"><div>text</div>more text</div></div>
+ <button id="btn_labelledby_mixed_block"
+ aria-labelledby="labelledby_mixed_block">4</button>
+ <br/>
+
+ <!-- the name from subtree, mixed content, table structure -->
+ <table><tr>
+ <td id="labelledby_mixed_table">text<span>space</span>text</td>
+ </tr></table>
+ <button id="btn_labelledby_mixed_table"
+ aria-labelledby="labelledby_mixed_table">5</button>
+ <br/>
+
+ <!-- the name from subtree, child img -->
+ <span id="labelledby_mixed_img">text<img alt="image"/></span>
+ <button id="btn_labelledby_mixed_img"
+ aria-labelledby="labelledby_mixed_img">6</button>
+ <br/>
+
+ <!-- the name from subtree, child inputs -->
+ <span id="labelledby_mixed_input">
+ <input type="button" id="input_button" title="input button"/>
+ <input type="submit" id="input_submit"/>
+ <input type="reset" id="input_reset"/>
+ <input type="image" id="input_image" title="input image"/>
+ </span>
+ <button id="btn_labelledby_mixed_input"
+ aria-labelledby="labelledby_mixed_input">7</button>
+ <br/>
+
+ <!-- the name from subtree, child object -->
+ <span id="labelledby_mixed_object">
+ <object data="about:blank" title="object"></object>
+ </span>
+ <button id="btn_labelledby_mixed_object"
+ aria-labelledby="labelledby_mixed_object">8</button>
+ <br/>
+
+ <!-- the name from subtree, child br -->
+ <span id="labelledby_mixed_br">text<br/>text</span>
+ <button id="btn_labelledby_mixed_br"
+ aria-labelledby="labelledby_mixed_br">9</button>
+ <br/>
+
+ <!-- the name from subtree, name from label content rather than from its title
+ attribute -->
+ <label for="from_label_ignoretitle"
+ title="Select your country of origin">Country:</label>
+ <select id="from_label_ignoretitle">
+ <option>Germany</option>
+ <option>Russia</option>
+ </select>
+
+ <!-- the name from subtree, name from html:p content rather than from its
+ title attribute -->
+ <p id="p_ignoretitle"
+ title="Select your country of origin">Choose country from.</p>
+ <select id="from_p_ignoretitle" aria-labelledby="p_ignoretitle">
+ <option>Germany</option>
+ <option>Russia</option>
+ </select>
+
+ <!-- the name from subtree, name from html:input value rather than from its
+ title attribute -->
+ <p id="from_input_ignoretitle" aria-labelledby="input_ignoretitle">Country</p>
+ <input id="input_ignoretitle"
+ value="Custom country"
+ title="Input your country of origin"/ >
+
+ <!-- name from subtree, surround control by spaces to not jamm the text -->
+ <label id="insert_spaces_around_control">
+ start<input value="value">end
+ </label>
+
+ <!-- no name from subtree because it holds whitespaces only -->
+ <a id="from_label_ignore_ws_subtree" href="about:" title="about">&nbsp;&nbsp; </a>
+
+ <!-- label element, label contains control -->
+ <label>text<button id="btn_label_inside">10</button>text</label>
+ <br/>
+
+ <!-- label element, label and the button are in the same form -->
+ <form>
+ <label for="btn_label_inform">in form</label>
+ <button id="btn_label_inform">11</button>
+ </form>
+
+ <!-- label element, label is outside of the form of the button -->
+ <label for="btn_label_outform">out form</label>
+ <form>
+ <button id="btn_label_outform">12</button>
+ </form>
+
+ <!-- label element, label and the button are in the same document -->
+ <label for="btn_label_indocument">in document</label>
+ <button id="btn_label_indocument">13</button>
+
+ <!-- multiple label elements for single button -->
+ <label for="btn_label_multi">label1</label>
+ <label for="btn_label_multi">label2</label>
+ <button id="btn_label_multi">button</button>
+
+ <!-- a label containing more than one controls -->
+ <label>
+ Enable <input id="ctrl_in_label_1" type="checkbox"> a
+ <input id="ctrl_in_label_2" type="button" value="button"> control
+ </label>
+
+ <!-- name from children -->
+ <span id="btn_children" role="button">14</span>
+
+ <!-- no name from content, ARIA role overrides this rule -->
+ <button id="btn_nonamefromcontent" role="img">1</button>
+
+ <!-- name from children, hidden children -->
+ <div role="listbox" tabindex="0">
+ <div id="lb_opt1_children_hidden" role="option" tabindex="0">
+ <span>i am visible</span>
+ <span style="display:none">i am hidden</span>
+ </div>
+ </div>
+
+ <table role="menu">
+ <tr role="menuitem" id="tablemenuitem">
+ <td>menuitem 1</td>
+ </tr>
+ <tr role="menuitem">
+ <td>menuitem 2</td>
+ </tr>
+ </table>
+
+ <label id="label_with_acronym">
+ <acronym title="O A T F">OATF</acronym>
+ <abbr title="World Wide Web">WWW</abbr>
+ </label>
+
+ <div id="testArticle" role="article" title="Test article">
+ <p>This is a paragraph inside the article.</p>
+ </div>
+
+ <!-- name from title attribute -->
+ <span id="btn_title" role="group" title="title">15</span>
+
+ <!-- A textarea nested in a label with a text child (bug #453371). -->
+ <form>
+ <label>Story
+ <textarea id="textareawithchild" name="name">Foo</textarea>
+ is ended.
+ </label>
+ </form>
+
+ <!-- controls having a value used as part of computed name -->
+ <input type="checkbox" id="ctrlvalue_progressbar:input">
+ <label for="ctrlvalue_progressbar:input">
+ foo <span role="progressbar"
+ aria-valuenow="5" aria-valuemin="1"
+ aria-valuemax="10">5</span> baz
+ </label>
+
+ <input type="checkbox" id="ctrlvalue_scrollbar:input" />
+ <label for="ctrlvalue_scrollbar:input">
+ foo <span role="scrollbar"
+ aria-valuenow="5" aria-valuemin="1"
+ aria-valuemax="10">5</span> baz
+ </label>
+
+ <input type="checkbox" id="ctrlvalue_slider:input">
+ <label for="ctrlvalue_slider:input">
+ foo <input role="slider" type="range"
+ value="5" min="1" max="10"
+ aria-valuenow="5" aria-valuemin="1"
+ aria-valuemax="10"> baz
+ </label>
+
+ <input type="checkbox" id="ctrlvalue_spinbutton:input">
+ <label for="ctrlvalue_spinbutton:input">
+ foo <input role="spinbutton" type="number"
+ value="5" min="1" max="10"
+ aria-valuenow="5" aria-valuemin="1"
+ aria-valuemax="10">
+ baz
+ </label>
+
+ <input type="checkbox" id="ctrlvalue_combobox:input">
+ <label for="ctrlvalue_combobox:input">
+ foo
+ <div role="combobox">
+ <div role="textbox"></div>
+ <ul role="listbox" style="list-style-type: none;">
+ <li role="option">1</li>
+ <li role="option" aria-selected="true">5</li>
+ <li role="option">3</li>
+ </ul>
+ </div>
+ baz
+ </label>
+
+ <!-- a label with a nested control in the start, middle and end -->
+ <form>
+ <!-- at the start (without and with whitespaces) -->
+ <label id="comboinstart"><select id="combo3">
+ <option>One</option>
+ <option>Two</option>
+ </select>
+ day(s).
+ </label>
+
+ <label id="textboxinstart">
+ <input id="textbox1" value="Two">
+ days.
+ </label>
+
+ <!-- in the middle -->
+ <label id="comboinmiddle">
+ Subscribe to
+ <select id="combo4" name="occupation">
+ <option>ATOM</option>
+ <option>RSS</option>
+ </select>
+ feed.
+ </label>
+
+ <label id="comboinmiddle2" for="checkbox">Play the
+ <select id="combo5">
+ <option>Haliluya</option>
+ <option>Hurra</option>
+ </select>
+ sound when new mail arrives
+ </label>
+ <input id="checkbox" type="checkbox" />
+
+ <label id="comboinmiddle3" for="combo6">Play the
+ <select id="combo6">
+ <option>Haliluya</option>
+ <option>Hurra</option>
+ </select>
+ sound when new mail arrives
+ </label>
+
+ <!-- at the end (without and with whitespaces) -->
+ <label id="comboinend">
+ This day was
+ <select id="combo7" name="occupation">
+ <option>sunny</option>
+ <option>rainy</option>
+ </select></label>
+
+ <label id="textboxinend">
+ This day was
+ <input id="textbox2" value="sunny">
+ </label>
+ </form>
+
+ <!-- placeholder -->
+ <input id="ph_password" type="password" value="" placeholder="a placeholder" />
+ <input id="ph_text" type="text" placeholder="a placeholder" />
+ <textarea id="ph_textarea" cols="5" placeholder="a placeholder"></textarea>
+
+ <!-- placeholder does not win -->
+ <input id="ph_text2" type="text" aria-label="a label" placeholder="meh" />
+ <textarea id="ph_textarea2" cols="5" aria-labelledby="ph_text2"
+ placeholder="meh"></textarea>
+
+ <label for="ph_text3">a label</label>
+ <input id="ph_text3" placeholder="meh" />
+
+ <p>Image:
+ <img id="img_eq" role="math" src="foo" alt="x^2 + y^2 + z^2">
+ <input type="image" id="input_img_eq" src="foo" alt="x^2 + y^2 + z^2">
+ </p>
+
+ <p>Text:
+ <span id="txt_eq" role="math" title="x^2 + y^2 + z^2">x<sup>2</sup> +
+ y<sup>2</sup> + z<sup>2</sup></span>
+
+ <!-- duplicate announcement -->
+ <div id="test_note" role="note">subtree</div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/name/test_general.xul b/accessible/tests/mochitest/name/test_general.xul
new file mode 100644
index 000000000..c144e6f4f
--- /dev/null
+++ b/accessible/tests/mochitest/name/test_general.xul
@@ -0,0 +1,382 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<?xml-stylesheet href="general.css"
+ type="text/css"?>
+
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessibility Name Calculating Test.">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ function doTest()
+ {
+ // aria-label
+
+ // Simple label provided via ARIA
+ testName("btn_simple_aria_label", "I am a button");
+
+ // aria-label and aria-labelledby, expect aria-labelledby
+ testName("btn_both_aria_labels", "text I am a button, two");
+
+ //////////////////////////////////////////////////////////////////////////
+ // aria-labelledby
+
+ // Single relation. The value of 'aria-labelledby' contains the ID of
+ // an element. Gets the name from text node of that element.
+ testName("btn_labelledby_text", "text");
+
+ // Multiple relations. The value of 'aria-labelledby' contains the IDs
+ // of elements. Gets the name from text nodes of those elements.
+ testName("btn_labelledby_texts", "text1 text2");
+
+ // Trick cases. Self and recursive referencing.
+ testName("rememberHistoryDays", "Remember 3 days");
+ testName("historyDays", "Remember 3 days");
+ testName("rememberAfter", "days");
+
+ // Anonymous content (see name.xbl#third)
+ var anonBtn = getAccessible("labelledby_box_anon").lastChild;
+ testName(anonBtn, "It's a cool button");
+
+ //////////////////////////////////////////////////////////////////////////
+ // Name from subtree (single relation labelled_by).
+
+ // Gets the name from text nodes contained by nested elements.
+ testName("btn_labelledby_mixed", "nomore text");
+
+ // Gets the name from text nodes and selected item of menulist
+ // (other items are ignored).
+ testName("btn_labelledby_mixed_menulist",
+ "nomore text selected item more text");
+
+ // Gets the name from text nodes contained by nested elements, ignores
+ // hidden elements (bug 443081).
+ testName("btn_labelledby_mixed_hidden_child", "nomore text2");
+
+ // Gets the name from hidden text nodes contained by nested elements,
+ // (label element is hidden entirely), (bug 443081)
+ testName("btn_labelledby_mixed_hidden", "lala more hidden text");
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Name for nsIDOMXULLabeledControlElement.
+
+ // Gets the name from @label attribute.
+ testName("btn_nsIDOMXULLabeledControlElement", "labeled element");
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Name for nsIDOMXULSelectControlItemElement.
+
+ // Gets the name from @label attribute.
+ testName("li_nsIDOMXULSelectControlItemElement", "select control item");
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Name if the XUL element doesn't implement nsIDOMXULSelectControlElement
+ // and has @label attribute.
+
+ testName("box_not_nsIDOMXULSelectControlElement", "box");
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Name from the label element.
+
+ // The label and button are placed on 2nd level relative common parent.
+ testName("btn_label_1", "label1");
+
+ // The label is on 1st, the button is on 5th level relative common parent.
+ testName("btn_label_2", "label2");
+
+ // The label and button are siblings.
+ testName("btn_label_3", "label3");
+
+ // Multiple labels for single button: XUL button takes the last one.
+ testName("btn_label_4", "label5");
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Name from the label element in anonymous content (see bug 362365).
+
+ // Get the name from anonymous label element for anonymous textbox
+ // (@anonid is used).
+ var ID = "box_label_anon1";
+ var box1Acc = testName(ID, null);
+ if (box1Acc) {
+ var textboxAcc = box1Acc.firstChild;
+ is(textboxAcc.name, "Label",
+ "Wrong label for anonymous textbox of " + ID);
+ }
+
+ // Get the name from anonymous label element for anonymous textbox
+ // (@anonid is used). Nested bindings.
+ ID = "box_label_anon2";
+ var box2Acc = testName(ID, null);
+ if (box2Acc) {
+ var textboxAcc = box2Acc.firstChild;
+ is(textboxAcc.name, "Label",
+ "Wrong label for anonymous textbox of " + ID);
+
+ var topTextboxAcc = box2Acc.lastChild;
+ is(topTextboxAcc.name, "Top textbox",
+ "Wrong label for anonymous textbox of " + ID);
+ }
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // tooltiptext (if nothing above isn't presented then tooltiptext is used)
+ testName("box_tooltiptext", "tooltiptext label");
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Name from the @title attribute of <toolbaritem/> (original bug 237249).
+
+ // Direct child of toolbaritem.
+ var textboxAcc = testName("toolbaritem_textbox", "ooospspss");
+
+ // Element from anonymous content of direct child of toolbaritem.
+ var entryAcc = textboxAcc.firstChild;
+ testRole(entryAcc, ROLE_ENTRY);
+ is(entryAcc.name, "ooospspss",
+ "Wrong name for text entry of autocomplete textbox 'toolbaritem_textbox'.");
+
+ // Child from subtree of toolbaritem.
+ testName("toolbaritem_hboxbutton", "ooospspss");
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Name from children
+
+ // ARIA role button is presented allowing the name calculation from
+ // children.
+ testName("box_children", "14");
+
+ // ARIA role option is presented allowing the name calculation from
+ // the visible children (bug 443081)
+ testName("lb_opt1_children_hidden", "i am visible");
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Name from aria-labelledby: menuitem label+ listitem label
+ testName("li_labelledby", "Show an Alert The moment the event starts");
+
+ //////////////////////////////////////////////////////////////////////////
+ // groupbox labeling from caption label or its sub tree
+ testName("groupbox", "Some caption");
+ testName("groupbox2", "Some caption");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=444279"
+ title="mochitest for accessible name calculating">
+ Mozilla Bug 444279
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=441991"
+ title="nsXULListitemAccessible::GetName prefers label \
+ attribute over aria-labelledby and doesn't allow recursion">
+ Mozilla Bug 441991
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+
+ <!-- aria-label, simple label -->
+ <button id="btn_simple_aria_label" aria-label="I am a button"/>
+
+ <!-- aria-label plus aria-labelledby -->
+ <button id="btn_both_aria_labels" aria-label="I am a button, two"
+ aria-labelledby="labelledby_text btn_both_aria_labels"/>
+
+ <!-- aria-labelledby, single relation -->
+ <description id="labelledby_text">text</description>
+ <button id="btn_labelledby_text"
+ aria-labelledby="labelledby_text"/>
+
+ <!-- aria-labelledby, multiple relations -->
+ <description id="labelledby_text1">text1</description>
+ <description id="labelledby_text2">text2</description>
+ <button id="btn_labelledby_texts"
+ aria-labelledby="labelledby_text1 labelledby_text2"/>
+
+ <!-- aria-labelledby, multiple relations -->
+ <box class="third" id="labelledby_box_anon" role="group" />
+
+ <!-- trick aria-labelledby -->
+ <checkbox id="rememberHistoryDays"
+ label="Remember "
+ aria-labelledby="rememberHistoryDays historyDays rememberAfter"/>
+ <textbox id="historyDays" type="number" size="3" value="3"
+ aria-labelledby="rememberHistoryDays historyDays rememberAfter"/>
+ <label id="rememberAfter">days</label>
+
+ <!-- the name from subtree, mixed content -->
+ <description id="labelledby_mixed">
+ no<description>more text</description>
+ </description>
+ <button id="btn_labelledby_mixed"
+ aria-labelledby="labelledby_mixed"/>
+
+ <!-- the name from subtree, mixed/hidden content -->
+ <description id="labelledby_mixed_hidden_child">no<description>more <description hidden="true">hidden</description>text2</description></description>
+ <button id="btn_labelledby_mixed_hidden_child"
+ aria-labelledby="labelledby_mixed_hidden_child"/>
+
+ <!-- the name from subtree, mixed/completely hidden content -->
+ <description id="labelledby_mixed_hidden"
+ hidden="true">lala <description>more hidden </description>text</description>
+ <button id="btn_labelledby_mixed_hidden"
+ aria-labelledby="labelledby_mixed_hidden"/>
+ <br/>
+
+ <!-- the name from subtree, mixed content, ignore items of menulist -->
+ <description id="labelledby_mixed_menulist">
+ no<description>more text</description>
+ <menulist>
+ <menupopup>
+ <menuitem label="selected item"/>
+ <menuitem label="item"/>
+ </menupopup>
+ </menulist>
+ more text
+ </description>
+ <button id="btn_labelledby_mixed_menulist"
+ aria-labelledby="labelledby_mixed_menulist"/>
+
+ <!-- nsIDOMXULLabeledControlElement -->
+ <button id="btn_nsIDOMXULLabeledControlElement"
+ label="labeled element"/>
+
+ <!-- nsIDOMXULSelectControlItemElement -->
+ <listbox>
+ <listitem id="li_nsIDOMXULSelectControlItemElement"
+ label="select control item"/>
+ </listbox>
+
+ <!-- not nsIDOMXULSelectControlElement -->
+ <box id="box_not_nsIDOMXULSelectControlElement" role="group" label="box"/>
+
+ <!-- label element -->
+ <hbox>
+ <box>
+ <label control="btn_label_1">label1</label>
+ </box>
+ <label control="btn_label_2">label2</label>
+ <box>
+ <button id="btn_label_1"/>
+ <box>
+ <box>
+ <box>
+ <button id="btn_label_2"/>
+ </box>
+ </box>
+ </box>
+ </box>
+ <label control="btn_label_3">label3</label>
+ <button id="btn_label_3"/>
+
+ <label control="btn_label_4">label4</label>
+ <label control="btn_label_4">label5</label>
+ <button id="btn_label_4"/>
+ </hbox>
+
+ <!-- label element, anonymous content -->
+ <box id="box_label_anon1"
+ class="first"
+ role="group"/>
+
+ <box id="box_label_anon2"
+ class="second"
+ role="group"/>
+
+ <!-- tooltiptext -->
+ <box id="box_tooltiptext"
+ role="group"
+ tooltiptext="tooltiptext label"/>
+
+ <!-- the name from @title of toolbaritem -->
+ <toolbar>
+ <toolbaritem title="ooospspss">
+ <textbox id="toolbaritem_textbox"
+ flex="1"
+ type="autocomplete"
+ enablehistory="true">
+ <hbox role="button" id="toolbaritem_hboxbutton">
+ <description value="button"/>
+ </hbox>
+ </textbox>
+ </toolbaritem>
+ </toolbar>
+
+ <!-- name from children -->
+ <box id="box_children" role="button">14</box>
+
+ <!-- name from children, hidden children -->
+ <vbox role="listbox" tabindex="0">
+ <hbox id="lb_opt1_children_hidden" role="option" tabindex="0">
+ <description>i am visible</description>
+ <description style="display:none">i am hidden</description>
+ </hbox>
+
+ <!-- Name from label or sub tree -->
+ <groupbox id="groupbox">
+ <caption label="Some caption" />
+ <checkbox label="some checkbox label" />
+ </groupbox>
+ <groupbox id="groupbox2">
+ <caption><label>Some caption</label></caption>
+ <checkbox label="some checkbox label" />
+ </groupbox>
+ </vbox>
+
+ <!-- bug 441991; create name from other menuitem label listitem's own label -->
+ <hbox>
+ <listbox>
+ <listitem id="li_labelledby"
+ label="The moment the event starts"
+ aria-labelledby="menuitem-DISPLAY li_labelledby"/>
+ </listbox>
+ <menulist>
+ <menupopup>
+ <menuitem id="menuitem-DISPLAY"
+ value="DISPLAY"
+ label="Show an Alert"/>
+ <menuitem id="menuitem-EMAIL"
+ value="EMAIL"
+ label="Send an E-mail"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+
+ </vbox> <!-- close tests area -->
+ </hbox> <!-- close main area -->
+</window>
+
diff --git a/accessible/tests/mochitest/name/test_link.html b/accessible/tests/mochitest/name/test_link.html
new file mode 100644
index 000000000..773e63731
--- /dev/null
+++ b/accessible/tests/mochitest/name/test_link.html
@@ -0,0 +1,89 @@
+<html>
+
+<head>
+ <title>nsIAccessible::name calculation for HTML links (html:a)</title>
+
+ <link rel="stylesheet"
+ type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ // aria-label
+ testName("aria_label", "anchor label");
+
+ // aria-labelledby
+ testName("aria_labelledby", "text");
+
+ // name from content
+ testName("namefromcontent", "1");
+
+ // name from content
+ testName("namefromimg", "img title");
+
+ // no name from content
+ testName("nonamefromcontent", null);
+
+ // title
+ testName("title", "title");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=459782"
+ title="nsIAccessible::name calculation for HTML links (html:a)">
+ Mozilla Bug 459782
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- aria-label -->
+ <a id="aria_label" href="mozilla.org"
+ aria-label="anchor label">1</a>
+ <br/>
+
+ <!-- aria-labelledby, preferred to html:label -->
+ <span id="text">text</span>
+ <label for="aria_labelledby">label</label>
+ <a id="aria_labelledby" href="mozilla.org"
+ aria-labelledby="text">1</a>
+ <br/>
+
+ <!-- name from content, preferred to @title -->
+ <a id="namefromcontent" href="mozilla.org"
+ title="title">1</a>
+ <br/>
+
+ <!-- name from content, preferred to @title -->
+ <a id="namefromimg" href="mozilla.org"
+ title="title"><img alt="img title" /></a>
+
+ <!-- no name from content, ARIA role overrides this rule -->
+ <a id="nonamefromcontent" href="mozilla.org" role="img">1</a>
+ <br/>
+
+ <!-- no content, name from @title -->
+ <a id="title" href="mozilla.org"
+ title="title"></a>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/name/test_list.html b/accessible/tests/mochitest/name/test_list.html
new file mode 100644
index 000000000..073b70dd2
--- /dev/null
+++ b/accessible/tests/mochitest/name/test_list.html
@@ -0,0 +1,89 @@
+<html>
+
+<head>
+ <title>nsIAccessible::name calculation for HTML li</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ /**
+ * Alter list item numbering and change list style type.
+ */
+ function bulletUpdate()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, getNode("list"))
+ ];
+
+ this.invoke = function bulletUpdate_invoke()
+ {
+ testName("li_end", "1. list end");
+
+ var li = document.createElement("li");
+ li.setAttribute("id", "li_start");
+ li.textContent = "list start";
+ getNode("list").insertBefore(li, getNode("li_end"));
+ }
+
+ this.finalCheck = function bulletUpdate_finalCheck()
+ {
+ testName("li_start", "1. list start");
+ testName("li_end", "2. list end");
+
+ // change list style type
+ var list = getNode("list");
+ list.setAttribute("style", "list-style-type: disc;");
+ getComputedStyle(list, "").color; // make style processing sync
+
+ testName("li_start", kDiscBulletText + "list start");
+ testName("li_end", kDiscBulletText + "list end");
+ }
+
+ this.getID = function bulletUpdate_getID()
+ {
+ return "Update bullet of list items";
+ }
+ }
+
+ var gQueue = null;
+ function doTest()
+ {
+ gQueue = new eventQueue();
+ gQueue.push(new bulletUpdate());
+ gQueue.invoke(); // SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=634200"
+ title="crash [@ nsIFrame::StyleVisibility() ]">
+ Mozilla Bug 634200
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <ol id="list">
+ <li id="li_end">list end</li>
+ </ol>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/name/test_markup.html b/accessible/tests/mochitest/name/test_markup.html
new file mode 100644
index 000000000..7b478e0ba
--- /dev/null
+++ b/accessible/tests/mochitest/name/test_markup.html
@@ -0,0 +1,60 @@
+<html>
+
+<head>
+ <title>nsIAccessible::name calculation for elements</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript"
+ src="markup.js"></script>
+
+ <script type="application/javascript">
+ // gA11yEventDumpID = "eventdump";
+ //gDumpToConsole = true;
+ //gA11yEventDumpToConsole = true;
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(testNames);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=459635"
+ title="nsIAccessible::name calculation for elements">
+ Bug 459635
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=666212"
+ title="summary attribute content mapped to accessible name in MSAA">
+ Bug 666212
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=786163"
+ title=" Sort out name calculation for HTML input buttons">
+ Bug 786163
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/name/test_svg.html b/accessible/tests/mochitest/name/test_svg.html
new file mode 100644
index 000000000..81dc0481e
--- /dev/null
+++ b/accessible/tests/mochitest/name/test_svg.html
@@ -0,0 +1,55 @@
+<html>
+
+<head>
+ <title>Accessible name and description for SVG elements</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest()
+ {
+ testName("svg1", "A name");
+ testDescr("svg1", "A description");
+ testName("svg2", "A tooltip");
+ testDescr("svg2", "");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=459357"
+ title="Support accessible name computation for SVG">
+ Mozilla Bug 459357
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="svg1">
+ <title>A name</title>
+ <desc>A description</title>
+ </svg>
+
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="svg2">
+ <desc>A tooltip</desc>
+ </svg>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/name/test_toolbaritem.xul b/accessible/tests/mochitest/name/test_toolbaritem.xul
new file mode 100644
index 000000000..8968964a9
--- /dev/null
+++ b/accessible/tests/mochitest/name/test_toolbaritem.xul
@@ -0,0 +1,84 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<?xml-stylesheet href="general.css"
+ type="text/css"?>
+
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessibility Name Calculating Test.">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+ <script type="application/javascript">
+ <![CDATA[
+ var gQueue = null;
+ function doTest() {
+ let ids = [];
+ for (let item of ["button", "textbox"]) {
+ ids.push(item + "withtooltip");
+ ids.push(item + "withouttooltip");
+ ids.push("nested" + item + "withtooltip");
+ ids.push("nested" + item + "withouttooltip");
+ }
+
+ for (let id of ids) {
+ if (id.endsWith("withtooltip")) {
+ testName(id, id, id + " should have individual name from its tooltip - ");
+ } else {
+ testName(id, "Toolbaritem title", id + " should have toolbaritem's title for a name - ");
+ }
+ }
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1216478"
+ title="Items with tooltips inside items with a label should use their own tooltip as an accessible name, not the ancestor's label">
+ Mozilla Bug 1216478
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <toolbox>
+ <toolbar>
+ <toolbaritem title="Toolbaritem title">
+ <toolbarbutton id="buttonwithtooltip" tooltiptext="buttonwithtooltip"/>
+ <toolbarbutton id="buttonwithouttooltip"/>
+ <textbox id="textboxwithtooltip" tooltiptext="textboxwithtooltip"/>
+ <textbox id="textboxwithouttooltip"/>
+ <vbox>
+ <toolbarbutton id="nestedbuttonwithtooltip" tooltiptext="nestedbuttonwithtooltip"/>
+ <toolbarbutton id="nestedbuttonwithouttooltip"/>
+ <textbox id="nestedtextboxwithtooltip" tooltiptext="nestedtextboxwithtooltip"/>
+ <textbox id="nestedtextboxwithouttooltip"/>
+ </vbox>
+ </toolbaritem>
+ </toolbar>
+ </toolbox>
+
+
+ </vbox> <!-- close tests area -->
+ </hbox> <!-- close main area -->
+</window>
diff --git a/accessible/tests/mochitest/name/test_tree.xul b/accessible/tests/mochitest/name/test_tree.xul
new file mode 100644
index 000000000..fcb50d151
--- /dev/null
+++ b/accessible/tests/mochitest/name/test_tree.xul
@@ -0,0 +1,211 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<?xml-stylesheet href="general.css"
+ type="text/css"?>
+
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessibility Name Calculating Test.">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ function treeTester(aID)
+ {
+ this.DOMNode = getNode(aID);
+
+ this.invoke = function treeTester_invoke()
+ {
+ this.DOMNode.view = new nsTreeTreeView();
+ }
+
+ this.check = function treeTester_check(aEvent)
+ {
+ var tree = {
+ role: ROLE_OUTLINE,
+ children: [
+ {
+ role: ROLE_LIST
+ },
+ {
+ role: ROLE_OUTLINEITEM,
+ children: [],
+ name: "row1col"
+ },
+ {
+ role: ROLE_OUTLINEITEM,
+ children: [],
+ name: "row2_col"
+ },
+ {
+ role: ROLE_OUTLINEITEM,
+ children: [],
+ name: "row2.1_col"
+ },
+ {
+ role: ROLE_OUTLINEITEM,
+ children: [],
+ name: "row2.2_col"
+ },
+ {
+ role: ROLE_OUTLINEITEM,
+ children: [],
+ name: "row3_col"
+ },
+ {
+ role: ROLE_OUTLINEITEM,
+ children: [],
+ name: "row4col"
+ }
+ ]
+ };
+ testAccessibleTree(this.DOMNode, tree);
+ }
+
+ this.getID = function treeTester_getID()
+ {
+ return "Tree name testing for " + aID;
+ }
+ }
+
+ function tableTester(aID, aIsTable, aCol1ID, aCol2ID)
+ {
+ this.DOMNode = getNode(aID);
+
+ this.invoke = function tableTester_invoke()
+ {
+ this.DOMNode.view = new nsTableTreeView(2);
+ }
+
+ this.check = function tableTester_check(aEvent)
+ {
+ var tree = {
+ role: aIsTable ? ROLE_TABLE : ROLE_TREE_TABLE,
+ children: [
+ {
+ role: ROLE_LIST
+ },
+ {
+ role: ROLE_ROW,
+ children: [
+ {
+ role: ROLE_GRID_CELL,
+ children: [],
+ name: "row0_" + aCol1ID
+ },
+ {
+ role: ROLE_GRID_CELL,
+ children: [],
+ name: "row0_" + aCol2ID
+ }
+ ],
+ name: "row0_" + aCol1ID + " row0_" + aCol2ID
+ },
+ {
+ role: ROLE_ROW,
+ children: [
+ {
+ role: ROLE_GRID_CELL,
+ children: [],
+ name: "row1_" + aCol1ID
+ },
+ {
+ role: ROLE_GRID_CELL,
+ children: [],
+ name: "row1_" + aCol2ID
+ }
+ ],
+ name: "row1_" + aCol1ID + " row1_" + aCol2ID
+ }
+ ]
+ };
+ testAccessibleTree(this.DOMNode, tree);
+ }
+
+ this.getID = function tableTester_getID()
+ {
+ return "Tree name testing for " + aID;
+ }
+ }
+
+ var gQueue = null;
+ function doTest()
+ {
+ var gQueue = new eventQueue(EVENT_REORDER);
+
+ gQueue.push(new treeTester("tree"));
+ gQueue.push(new tableTester("table", true, "t_col1", "t_col2"));
+ gQueue.push(new tableTester("treetable", false, "tt_col1", "tt_col2"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=546812"
+ title="Treegrid row accessible shouldn't inherit name from tree accessible">
+ Mozilla Bug 546812
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=664376"
+ title="Table rows of XUL trees no longer containing cell content as accessible name">
+ Mozilla Bug 664376
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <tree id="table" flex="1">
+ <treecols>
+ <treecol id="t_col1" flex="1" label="column"/>
+ <treecol id="t_col2" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <tree id="treetable" flex="1">
+ <treecols>
+ <treecol id="tt_col1" flex="1" label="column" primary="true"/>
+ <treecol id="tt_col2" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ </vbox> <!-- close tests area -->
+ </hbox> <!-- close main area -->
+</window>
diff --git a/accessible/tests/mochitest/pivot.js b/accessible/tests/mochitest/pivot.js
new file mode 100644
index 000000000..7bb1d81ac
--- /dev/null
+++ b/accessible/tests/mochitest/pivot.js
@@ -0,0 +1,551 @@
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+////////////////////////////////////////////////////////////////////////////////
+// Constants
+
+const PREFILTER_INVISIBLE = nsIAccessibleTraversalRule.PREFILTER_INVISIBLE;
+const PREFILTER_ARIA_HIDDEN = nsIAccessibleTraversalRule.PREFILTER_ARIA_HIDDEN;
+const PREFILTER_TRANSPARENT = nsIAccessibleTraversalRule.PREFILTER_TRANSPARENT;
+const FILTER_MATCH = nsIAccessibleTraversalRule.FILTER_MATCH;
+const FILTER_IGNORE = nsIAccessibleTraversalRule.FILTER_IGNORE;
+const FILTER_IGNORE_SUBTREE = nsIAccessibleTraversalRule.FILTER_IGNORE_SUBTREE;
+const CHAR_BOUNDARY = nsIAccessiblePivot.CHAR_BOUNDARY;
+const WORD_BOUNDARY = nsIAccessiblePivot.WORD_BOUNDARY;
+
+const NS_ERROR_NOT_IN_TREE = 0x80780026;
+const NS_ERROR_INVALID_ARG = 0x80070057;
+
+////////////////////////////////////////////////////////////////////////////////
+// Traversal rules
+
+/**
+ * Rule object to traverse all focusable nodes and text nodes.
+ */
+var HeadersTraversalRule =
+{
+ getMatchRoles: function(aRules)
+ {
+ aRules.value = [ROLE_HEADING];
+ return aRules.value.length;
+ },
+
+ preFilter: PREFILTER_INVISIBLE,
+
+ match: function(aAccessible)
+ {
+ return FILTER_MATCH;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([nsIAccessibleTraversalRule])
+}
+
+/**
+ * Traversal rule for all focusable nodes or leafs.
+ */
+var ObjectTraversalRule =
+{
+ getMatchRoles: function(aRules)
+ {
+ aRules.value = [];
+ return 0;
+ },
+
+ preFilter: PREFILTER_INVISIBLE | PREFILTER_ARIA_HIDDEN | PREFILTER_TRANSPARENT,
+
+ match: function(aAccessible)
+ {
+ var rv = FILTER_IGNORE;
+ var role = aAccessible.role;
+ if (hasState(aAccessible, STATE_FOCUSABLE) &&
+ (role != ROLE_DOCUMENT && role != ROLE_INTERNAL_FRAME))
+ rv = FILTER_IGNORE_SUBTREE | FILTER_MATCH;
+ else if (aAccessible.childCount == 0 &&
+ role != ROLE_STATICTEXT && aAccessible.name.trim())
+ rv = FILTER_MATCH;
+
+ return rv;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([nsIAccessibleTraversalRule])
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// Virtual state invokers and checkers
+
+/**
+ * A checker for virtual cursor changed events.
+ */
+function VCChangedChecker(aDocAcc, aIdOrNameOrAcc, aTextOffsets, aPivotMoveMethod,
+ aIsFromUserInput)
+{
+ this.__proto__ = new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc);
+
+ this.match = function VCChangedChecker_match(aEvent)
+ {
+ var event = null;
+ try {
+ event = aEvent.QueryInterface(nsIAccessibleVirtualCursorChangeEvent);
+ } catch (e) {
+ return false;
+ }
+
+ var expectedReason = VCChangedChecker.methodReasonMap[aPivotMoveMethod] ||
+ nsIAccessiblePivot.REASON_NONE;
+
+ return event.reason == expectedReason;
+ };
+
+ this.check = function VCChangedChecker_check(aEvent)
+ {
+ SimpleTest.info("VCChangedChecker_check");
+
+ var event = null;
+ try {
+ event = aEvent.QueryInterface(nsIAccessibleVirtualCursorChangeEvent);
+ } catch (e) {
+ SimpleTest.ok(false, "Does not support correct interface: " + e);
+ }
+
+ var position = aDocAcc.virtualCursor.position;
+ var idMatches = position && position.DOMNode.id == aIdOrNameOrAcc;
+ var nameMatches = position && position.name == aIdOrNameOrAcc;
+ var accMatches = position == aIdOrNameOrAcc;
+
+ SimpleTest.ok(idMatches || nameMatches || accMatches, "id or name matches",
+ "expecting " + aIdOrNameOrAcc + ", got '" +
+ prettyName(position));
+
+ SimpleTest.is(aEvent.isFromUserInput, aIsFromUserInput,
+ "Expected user input is " + aIsFromUserInput + '\n');
+
+ if (aTextOffsets) {
+ SimpleTest.is(aDocAcc.virtualCursor.startOffset, aTextOffsets[0],
+ "wrong start offset");
+ SimpleTest.is(aDocAcc.virtualCursor.endOffset, aTextOffsets[1],
+ "wrong end offset");
+ }
+
+ var prevPosAndOffset = VCChangedChecker.
+ getPreviousPosAndOffset(aDocAcc.virtualCursor);
+
+ if (prevPosAndOffset) {
+ SimpleTest.is(event.oldAccessible, prevPosAndOffset.position,
+ "previous position does not match");
+ SimpleTest.is(event.oldStartOffset, prevPosAndOffset.startOffset,
+ "previous start offset does not match");
+ SimpleTest.is(event.oldEndOffset, prevPosAndOffset.endOffset,
+ "previous end offset does not match");
+ }
+ };
+}
+
+VCChangedChecker.prevPosAndOffset = {};
+
+VCChangedChecker.storePreviousPosAndOffset =
+ function storePreviousPosAndOffset(aPivot)
+{
+ VCChangedChecker.prevPosAndOffset[aPivot] =
+ {position: aPivot.position,
+ startOffset: aPivot.startOffset,
+ endOffset: aPivot.endOffset};
+};
+
+VCChangedChecker.getPreviousPosAndOffset =
+ function getPreviousPosAndOffset(aPivot)
+{
+ return VCChangedChecker.prevPosAndOffset[aPivot];
+};
+
+VCChangedChecker.methodReasonMap = {
+ 'moveNext': nsIAccessiblePivot.REASON_NEXT,
+ 'movePrevious': nsIAccessiblePivot.REASON_PREV,
+ 'moveFirst': nsIAccessiblePivot.REASON_FIRST,
+ 'moveLast': nsIAccessiblePivot.REASON_LAST,
+ 'setTextRange': nsIAccessiblePivot.REASON_TEXT,
+ 'moveNextByText': nsIAccessiblePivot.REASON_TEXT,
+ 'movePreviousByText': nsIAccessiblePivot.REASON_TEXT,
+ 'moveToPoint': nsIAccessiblePivot.REASON_POINT
+};
+
+/**
+ * Set a text range in the pivot and wait for virtual cursor change event.
+ *
+ * @param aDocAcc [in] document that manages the virtual cursor
+ * @param aTextAccessible [in] accessible to set to virtual cursor's position
+ * @param aTextOffsets [in] start and end offsets of text range to set in
+ * virtual cursor.
+ */
+function setVCRangeInvoker(aDocAcc, aTextAccessible, aTextOffsets)
+{
+ this.invoke = function virtualCursorChangedInvoker_invoke()
+ {
+ VCChangedChecker.
+ storePreviousPosAndOffset(aDocAcc.virtualCursor);
+ SimpleTest.info(prettyName(aTextAccessible) + " " + aTextOffsets);
+ aDocAcc.virtualCursor.setTextRange(aTextAccessible,
+ aTextOffsets[0],
+ aTextOffsets[1]);
+ };
+
+ this.getID = function setVCRangeInvoker_getID()
+ {
+ return "Set offset in " + prettyName(aTextAccessible) +
+ " to (" + aTextOffsets[0] + ", " + aTextOffsets[1] + ")";
+ };
+
+ this.eventSeq = [
+ new VCChangedChecker(aDocAcc, aTextAccessible, aTextOffsets, "setTextRange", true)
+ ];
+}
+
+/**
+ * Move the pivot and wait for virtual cursor change event.
+ *
+ * @param aDocAcc [in] document that manages the virtual cursor
+ * @param aPivotMoveMethod [in] method to test (ie. "moveNext", "moveFirst", etc.)
+ * @param aRule [in] traversal rule object
+ * @param aIdOrNameOrAcc [in] id, accessible or accessible name to expect
+ * virtual cursor to land on after performing move method.
+ * false if no move is expected.
+ * @param aIsFromUserInput [in] set user input flag when invoking method, and
+ * expect it in the event.
+ */
+function setVCPosInvoker(aDocAcc, aPivotMoveMethod, aRule, aIdOrNameOrAcc,
+ aIsFromUserInput)
+{
+ var expectMove = (aIdOrNameOrAcc != false);
+ this.invoke = function virtualCursorChangedInvoker_invoke()
+ {
+ VCChangedChecker.
+ storePreviousPosAndOffset(aDocAcc.virtualCursor);
+ if (aPivotMoveMethod && aRule) {
+ var moved = false;
+ switch (aPivotMoveMethod) {
+ case 'moveFirst':
+ case 'moveLast':
+ moved = aDocAcc.virtualCursor[aPivotMoveMethod](aRule,
+ aIsFromUserInput === undefined ? true : aIsFromUserInput);
+ break;
+ case 'moveNext':
+ case 'movePrevious':
+ moved = aDocAcc.virtualCursor[aPivotMoveMethod](aRule,
+ aDocAcc.virtualCursor.position, false,
+ aIsFromUserInput === undefined ? true : aIsFromUserInput);
+ break;
+ }
+ SimpleTest.is(!!moved, !!expectMove,
+ "moved pivot with " + aPivotMoveMethod +
+ " to " + aIdOrNameOrAcc);
+ } else {
+ aDocAcc.virtualCursor.position = getAccessible(aIdOrNameOrAcc);
+ }
+ };
+
+ this.getID = function setVCPosInvoker_getID()
+ {
+ return "Do " + (expectMove ? "" : "no-op ") + aPivotMoveMethod;
+ };
+
+ if (expectMove) {
+ this.eventSeq = [
+ new VCChangedChecker(aDocAcc, aIdOrNameOrAcc, null, aPivotMoveMethod,
+ aIsFromUserInput === undefined ? !!aPivotMoveMethod : aIsFromUserInput)
+ ];
+ } else {
+ this.eventSeq = [];
+ this.unexpectedEventSeq = [
+ new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc)
+ ];
+ }
+}
+
+/**
+ * Move the pivot by text and wait for virtual cursor change event.
+ *
+ * @param aDocAcc [in] document that manages the virtual cursor
+ * @param aPivotMoveMethod [in] method to test (ie. "moveNext", "moveFirst", etc.)
+ * @param aBoundary [in] boundary constant
+ * @param aTextOffsets [in] start and end offsets of text range to set in
+ * virtual cursor.
+ * @param aIdOrNameOrAcc [in] id, accessible or accessible name to expect
+ * virtual cursor to land on after performing move method.
+ * false if no move is expected.
+ * @param aIsFromUserInput [in] set user input flag when invoking method, and
+ * expect it in the event.
+ */
+function setVCTextInvoker(aDocAcc, aPivotMoveMethod, aBoundary, aTextOffsets,
+ aIdOrNameOrAcc, aIsFromUserInput)
+{
+ var expectMove = (aIdOrNameOrAcc != false);
+ this.invoke = function virtualCursorChangedInvoker_invoke()
+ {
+ VCChangedChecker.storePreviousPosAndOffset(aDocAcc.virtualCursor);
+ SimpleTest.info(aDocAcc.virtualCursor.position);
+ var moved = aDocAcc.virtualCursor[aPivotMoveMethod](aBoundary,
+ aIsFromUserInput === undefined ? true : false);
+ SimpleTest.is(!!moved, !!expectMove,
+ "moved pivot by text with " + aPivotMoveMethod +
+ " to " + aIdOrNameOrAcc);
+ };
+
+ this.getID = function setVCPosInvoker_getID()
+ {
+ return "Do " + (expectMove ? "" : "no-op ") + aPivotMoveMethod + " in " +
+ prettyName(aIdOrNameOrAcc) + ", " + boundaryToString(aBoundary) +
+ ", [" + aTextOffsets + "]";
+ };
+
+ if (expectMove) {
+ this.eventSeq = [
+ new VCChangedChecker(aDocAcc, aIdOrNameOrAcc, aTextOffsets, aPivotMoveMethod,
+ aIsFromUserInput === undefined ? true : aIsFromUserInput)
+ ];
+ } else {
+ this.eventSeq = [];
+ this.unexpectedEventSeq = [
+ new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc)
+ ];
+ }
+}
+
+
+/**
+ * Move the pivot to the position under the point.
+ *
+ * @param aDocAcc [in] document that manages the virtual cursor
+ * @param aX [in] screen x coordinate
+ * @param aY [in] screen y coordinate
+ * @param aIgnoreNoMatch [in] don't unset position if no object was found at
+ * point.
+ * @param aRule [in] traversal rule object
+ * @param aIdOrNameOrAcc [in] id, accessible or accessible name to expect
+ * virtual cursor to land on after performing move method.
+ * false if no move is expected.
+ */
+function moveVCCoordInvoker(aDocAcc, aX, aY, aIgnoreNoMatch,
+ aRule, aIdOrNameOrAcc)
+{
+ var expectMove = (aIdOrNameOrAcc != false);
+ this.invoke = function virtualCursorChangedInvoker_invoke()
+ {
+ VCChangedChecker.
+ storePreviousPosAndOffset(aDocAcc.virtualCursor);
+ var moved = aDocAcc.virtualCursor.moveToPoint(aRule, aX, aY,
+ aIgnoreNoMatch);
+ SimpleTest.ok((expectMove && moved) || (!expectMove && !moved),
+ "moved pivot");
+ };
+
+ this.getID = function setVCPosInvoker_getID()
+ {
+ return "Do " + (expectMove ? "" : "no-op ") + "moveToPoint " + aIdOrNameOrAcc;
+ };
+
+ if (expectMove) {
+ this.eventSeq = [
+ new VCChangedChecker(aDocAcc, aIdOrNameOrAcc, null, 'moveToPoint', true)
+ ];
+ } else {
+ this.eventSeq = [];
+ this.unexpectedEventSeq = [
+ new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc)
+ ];
+ }
+}
+
+/**
+ * Change the pivot modalRoot
+ *
+ * @param aDocAcc [in] document that manages the virtual cursor
+ * @param aModalRootAcc [in] accessible of the modal root, or null
+ * @param aExpectedResult [in] error result expected. 0 if expecting success
+ */
+function setModalRootInvoker(aDocAcc, aModalRootAcc, aExpectedResult)
+{
+ this.invoke = function setModalRootInvoker_invoke()
+ {
+ var errorResult = 0;
+ try {
+ aDocAcc.virtualCursor.modalRoot = aModalRootAcc;
+ } catch (x) {
+ SimpleTest.ok(
+ x.result, "Unexpected exception when changing modal root: " + x);
+ errorResult = x.result;
+ }
+
+ SimpleTest.is(errorResult, aExpectedResult,
+ "Did not get expected result when changing modalRoot");
+ };
+
+ this.getID = function setModalRootInvoker_getID()
+ {
+ return "Set modalRoot to " + prettyName(aModalRootAcc);
+ };
+
+ this.eventSeq = [];
+ this.unexpectedEventSeq = [
+ new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc)
+ ];
+}
+
+/**
+ * Add invokers to a queue to test a rule and an expected sequence of element ids
+ * or accessible names for that rule in the given document.
+ *
+ * @param aQueue [in] event queue in which to push invoker sequence.
+ * @param aDocAcc [in] the managing document of the virtual cursor we are
+ * testing
+ * @param aRule [in] the traversal rule to use in the invokers
+ * @param aModalRoot [in] a modal root to use in this traversal sequence
+ * @param aSequence [in] a sequence of accessible names or element ids to expect
+ * with the given rule in the given document
+ */
+function queueTraversalSequence(aQueue, aDocAcc, aRule, aModalRoot, aSequence)
+{
+ aDocAcc.virtualCursor.position = null;
+
+ // Add modal root (if any)
+ aQueue.push(new setModalRootInvoker(aDocAcc, aModalRoot, 0));
+
+ aQueue.push(new setVCPosInvoker(aDocAcc, "moveFirst", aRule, aSequence[0]));
+
+ for (var i = 1; i < aSequence.length; i++) {
+ var invoker =
+ new setVCPosInvoker(aDocAcc, "moveNext", aRule, aSequence[i]);
+ aQueue.push(invoker);
+ }
+
+ // No further more matches for given rule, expect no virtual cursor changes.
+ aQueue.push(new setVCPosInvoker(aDocAcc, "moveNext", aRule, false));
+
+ for (var i = aSequence.length-2; i >= 0; i--) {
+ var invoker =
+ new setVCPosInvoker(aDocAcc, "movePrevious", aRule, aSequence[i]);
+ aQueue.push(invoker);
+ }
+
+ // No previous more matches for given rule, expect no virtual cursor changes.
+ aQueue.push(new setVCPosInvoker(aDocAcc, "movePrevious", aRule, false));
+
+ aQueue.push(new setVCPosInvoker(aDocAcc, "moveLast", aRule,
+ aSequence[aSequence.length - 1]));
+
+ // No further more matches for given rule, expect no virtual cursor changes.
+ aQueue.push(new setVCPosInvoker(aDocAcc, "moveNext", aRule, false));
+
+ // set isFromUserInput to false, just to test..
+ aQueue.push(new setVCPosInvoker(aDocAcc, "moveFirst", aRule, aSequence[0], false));
+
+ // No previous more matches for given rule, expect no virtual cursor changes.
+ aQueue.push(new setVCPosInvoker(aDocAcc, "movePrevious", aRule, false));
+
+ // Remove modal root (if any).
+ aQueue.push(new setModalRootInvoker(aDocAcc, null, 0));
+}
+
+/**
+ * A checker for removing an accessible while the virtual cursor is on it.
+ */
+function removeVCPositionChecker(aDocAcc, aHiddenParentAcc)
+{
+ this.__proto__ = new invokerChecker(EVENT_REORDER, aHiddenParentAcc);
+
+ this.check = function removeVCPositionChecker_check(aEvent) {
+ var errorResult = 0;
+ try {
+ aDocAcc.virtualCursor.moveNext(ObjectTraversalRule);
+ } catch (x) {
+ errorResult = x.result;
+ }
+ SimpleTest.is(
+ errorResult, NS_ERROR_NOT_IN_TREE,
+ "Expecting NOT_IN_TREE error when moving pivot from invalid position.");
+ };
+}
+
+/**
+ * Put the virtual cursor's position on an object, and then remove it.
+ *
+ * @param aDocAcc [in] document that manages the virtual cursor
+ * @param aPosNode [in] DOM node to hide after virtual cursor's position is
+ * set to it.
+ */
+function removeVCPositionInvoker(aDocAcc, aPosNode)
+{
+ this.accessible = getAccessible(aPosNode);
+ this.invoke = function removeVCPositionInvoker_invoke()
+ {
+ aDocAcc.virtualCursor.position = this.accessible;
+ aPosNode.parentNode.removeChild(aPosNode);
+ };
+
+ this.getID = function removeVCPositionInvoker_getID()
+ {
+ return "Bring virtual cursor to accessible, and remove its DOM node.";
+ };
+
+ this.eventSeq = [
+ new removeVCPositionChecker(aDocAcc, this.accessible.parent)
+ ];
+}
+
+/**
+ * A checker for removing the pivot root and then calling moveFirst, and
+ * checking that an exception is thrown.
+ */
+function removeVCRootChecker(aPivot)
+{
+ this.__proto__ = new invokerChecker(EVENT_REORDER, aPivot.root.parent);
+
+ this.check = function removeVCRootChecker_check(aEvent) {
+ var errorResult = 0;
+ try {
+ aPivot.moveLast(ObjectTraversalRule);
+ } catch (x) {
+ errorResult = x.result;
+ }
+ SimpleTest.is(
+ errorResult, NS_ERROR_NOT_IN_TREE,
+ "Expecting NOT_IN_TREE error when moving pivot from invalid position.");
+ };
+}
+
+/**
+ * Create a pivot, remove its root, and perform an operation where the root is
+ * needed.
+ *
+ * @param aRootNode [in] DOM node of which accessible will be the root of the
+ * pivot. Should have more than one child.
+ */
+function removeVCRootInvoker(aRootNode)
+{
+ this.pivot = gAccService.createAccessiblePivot(getAccessible(aRootNode));
+ this.invoke = function removeVCRootInvoker_invoke()
+ {
+ this.pivot.position = this.pivot.root.firstChild;
+ aRootNode.parentNode.removeChild(aRootNode);
+ };
+
+ this.getID = function removeVCRootInvoker_getID()
+ {
+ return "Remove root of pivot from tree.";
+ };
+
+ this.eventSeq = [
+ new removeVCRootChecker(this.pivot)
+ ];
+}
+
+/**
+ * A debug utility for writing proper sequences for queueTraversalSequence.
+ */
+function dumpTraversalSequence(aPivot, aRule)
+{
+ var sequence = [];
+ if (aPivot.moveFirst(aRule)) {
+ do {
+ sequence.push("'" + prettyName(aPivot.position) + "'");
+ } while (aPivot.moveNext(aRule))
+ }
+ SimpleTest.info("\n[" + sequence.join(", ") + "]\n");
+}
diff --git a/accessible/tests/mochitest/pivot/a11y.ini b/accessible/tests/mochitest/pivot/a11y.ini
new file mode 100644
index 000000000..8add46094
--- /dev/null
+++ b/accessible/tests/mochitest/pivot/a11y.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+support-files =
+ doc_virtualcursor.html
+ doc_virtualcursor_text.html
+ !/accessible/tests/mochitest/*.js
+
+[test_virtualcursor.html]
+[test_virtualcursor_text.html]
diff --git a/accessible/tests/mochitest/pivot/doc_virtualcursor.html b/accessible/tests/mochitest/pivot/doc_virtualcursor.html
new file mode 100644
index 000000000..a456f2dfc
--- /dev/null
+++ b/accessible/tests/mochitest/pivot/doc_virtualcursor.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Pivot test document</title>
+ <meta charset="utf-8" />
+</head>
+<body>
+ <h1 id="heading-1-1">Main Title</h1>
+ <h2 id="heading-2-1" aria-hidden="true">First Section Title</h2>
+ <p id="paragraph-1">
+ Lorem ipsum <strong>dolor</strong> sit amet. Integer vitae urna
+ leo, id <a href="#">semper</a> nulla.
+ </p>
+ <h2 id="heading-2-2" aria-hidden="undefined">Second Section Title</h2>
+ <p id="paragraph-2" aria-hidden="">
+ Sed accumsan luctus lacus, vitae mollis arcu tristique vulputate.</p>
+ <p id="paragraph-3" aria-hidden="true">
+ <a href="#" id="hidden-link">Maybe</a> it was the other <i>George Michael</i>.
+ You know, the <a href="#">singer-songwriter</a>.
+ </p>
+ <p style="opacity: 0;" id="paragraph-4">
+ This is completely transparent
+ </p>
+ <iframe
+ src="data:text/html,<html><body>An <i>embedded</i> document.</body></html>">
+ </iframe>
+ <div id="hide-me">Hide me</div>
+ <p id="links" aria-hidden="false">
+ <a href="http://mozilla.org" title="Link 1 title">Link 1</a>
+ <a href="http://mozilla.org" title="Link 2 title">Link 2</a>
+ <a href="http://mozilla.org" title="Link 3 title">Link 3</a>
+ </p>
+ <ul>
+ <li>Hello<span> </span></li>
+ <li>World</li>
+ </ul>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/pivot/doc_virtualcursor_text.html b/accessible/tests/mochitest/pivot/doc_virtualcursor_text.html
new file mode 100644
index 000000000..aba87bbd8
--- /dev/null
+++ b/accessible/tests/mochitest/pivot/doc_virtualcursor_text.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Pivot test document</title>
+ <meta charset="utf-8" />
+</head>
+<body>
+ <div id="start-block">This is the very beginning.</div>
+ <p id="paragraph-1">
+ This <b>is</b> <a id="p1-link-1" href="#">the</a> test of text.
+ </p>
+ <div id="section-1">A <a id="s1-link-1" href="#">multiword link</a> is here. <a id="s1-link-2" href="#">We</a> will traverse</div>
+ <div id="section-2">into, out, and between the subtrees.</div>
+ <p id="paragraph-2">Singularity.</p>
+ <table>
+ <tr>
+ <td id="cell-1">Magical</td>
+ <td id="cell-2">unicorns</td>
+ </tr>
+ <tr>
+ <td id="cell-3">and wizards</td>
+ <td id="cell-4">really exist.</td>
+ </tr>
+ </table>
+ <div id="section-3">Endless fun!</div>
+ <p id="paragraph-3">Objects<a id="p3-link-1" href="#">adjacent</a>to <a id="p3-link-2" href="#">each</a><a id="p3-link-3" href="#">other</a> should be separate.</p>
+ <div id="end-block">End!</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/pivot/test_virtualcursor.html b/accessible/tests/mochitest/pivot/test_virtualcursor.html
new file mode 100644
index 000000000..2fb339964
--- /dev/null
+++ b/accessible/tests/mochitest/pivot/test_virtualcursor.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Tests pivot functionality in virtual cursors</title>
+ <meta charset="utf-8" />
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/chrome-harness.js">
+ </script>
+
+ <script type="application/javascript" src="../common.js"></script>
+ <script type="application/javascript" src="../browser.js"></script>
+ <script type="application/javascript" src="../events.js"></script>
+ <script type="application/javascript" src="../role.js"></script>
+ <script type="application/javascript" src="../states.js"></script>
+ <script type="application/javascript" src="../pivot.js"></script>
+ <script type="application/javascript" src="../layout.js"></script>
+
+ <script type="application/javascript">
+ var gBrowserWnd = null;
+ var gQueue = null;
+
+ function doTest()
+ {
+ var rootAcc = getAccessible(browserDocument(), [nsIAccessibleDocument]);
+ ok(rootAcc.virtualCursor,
+ "root document does not have virtualCursor");
+
+ var doc = currentTabDocument();
+ var docAcc = getAccessible(doc, [nsIAccessibleDocument]);
+
+ // Test that embedded documents have their own virtual cursor.
+ is(docAcc.childDocumentCount, 1, "Expecting one child document");
+ ok(docAcc.getChildDocumentAt(0).virtualCursor,
+ "child document does not have virtualCursor");
+
+ gQueue = new eventQueue();
+
+ gQueue.onFinish = function onFinish()
+ {
+ closeBrowserWindow();
+ }
+
+ queueTraversalSequence(gQueue, docAcc, HeadersTraversalRule, null,
+ ['heading-1-1', 'heading-2-1', 'heading-2-2']);
+
+ queueTraversalSequence(
+ gQueue, docAcc, ObjectTraversalRule, null,
+ ['Main Title', 'Lorem ipsum ',
+ 'dolor', ' sit amet. Integer vitae urna leo, id ',
+ 'semper', ' nulla. ', 'Second Section Title',
+ 'Sed accumsan luctus lacus, vitae mollis arcu tristique vulputate.',
+ 'An ', 'embedded', ' document.', 'Hide me', 'Link 1', 'Link 2',
+ 'Link 3', 'Hello', 'World']);
+
+ // Just a random smoke test to see if our setTextRange works.
+ gQueue.push(
+ new setVCRangeInvoker(
+ docAcc,
+ getAccessible(doc.getElementById('paragraph-2'), nsIAccessibleText),
+ [2,6]));
+
+ gQueue.push(new removeVCPositionInvoker(
+ docAcc, doc.getElementById('hide-me')));
+
+ gQueue.push(new removeVCRootInvoker(
+ doc.getElementById('links')));
+
+ var [x, y] = getBounds(getAccessible(doc.getElementById('heading-1-1')));
+ gQueue.push(new moveVCCoordInvoker(docAcc, x + 1, y + 1, true,
+ HeadersTraversalRule, 'heading-1-1'));
+
+ // Already on the point, so we should not get a move event.
+ gQueue.push(new moveVCCoordInvoker(docAcc, x + 1, y + 1, true,
+ HeadersTraversalRule, false));
+
+ // Attempting a coordinate outside any header, should not move.
+ gQueue.push(new moveVCCoordInvoker(docAcc, x - 1, y - 1, true,
+ HeadersTraversalRule, false));
+
+ // Attempting a coordinate outside any header, should move to null
+ gQueue.push(new moveVCCoordInvoker(docAcc, x - 1, y - 1, false,
+ HeadersTraversalRule, null));
+
+ queueTraversalSequence(
+ gQueue, docAcc, ObjectTraversalRule,
+ getAccessible(doc.getElementById('paragraph-1')),
+ ['Lorem ipsum ', 'dolor', ' sit amet. Integer vitae urna leo, id ',
+ 'semper', ' nulla. ']);
+
+ gQueue.push(new setModalRootInvoker(docAcc, docAcc.parent,
+ NS_ERROR_INVALID_ARG));
+
+ // Put cursor in an ignored subtree
+ // set isFromUserInput to false, just to test..
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById("hidden-link")),
+ false));
+ // Next item shoud be outside of that subtree
+ gQueue.push(new setVCPosInvoker(docAcc, "moveNext", ObjectTraversalRule, "An "));
+
+ gQueue.invoke();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(function () {
+ /* We open a new browser because we need to test with a top-level content
+ document. */
+ openBrowserWindow(
+ doTest,
+ getRootDirectory(window.location.href) + "doc_virtualcursor.html");
+ });
+ </script>
+</head>
+<body id="body">
+
+ <a target="_blank"
+ title="Introduce virtual cursor/soft focus functionality to a11y API"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=698823">Mozilla Bug 698823</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/pivot/test_virtualcursor_text.html b/accessible/tests/mochitest/pivot/test_virtualcursor_text.html
new file mode 100644
index 000000000..1c11094fc
--- /dev/null
+++ b/accessible/tests/mochitest/pivot/test_virtualcursor_text.html
@@ -0,0 +1,241 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Tests pivot functionality in virtual cursors</title>
+ <meta charset="utf-8" />
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/chrome-harness.js">
+ </script>
+
+ <script type="application/javascript" src="../common.js"></script>
+ <script type="application/javascript" src="../text.js"></script>
+ <script type="application/javascript" src="../browser.js"></script>
+ <script type="application/javascript" src="../events.js"></script>
+ <script type="application/javascript" src="../role.js"></script>
+ <script type="application/javascript" src="../states.js"></script>
+ <script type="application/javascript" src="../pivot.js"></script>
+ <script type="application/javascript" src="../layout.js"></script>
+
+ <script type="application/javascript">
+ var gBrowserWnd = null;
+ var gQueue = null;
+
+ function doTest()
+ {
+ var doc = currentTabDocument();
+ var docAcc = getAccessible(doc, [nsIAccessibleDocument]);
+
+ gQueue = new eventQueue();
+
+ gQueue.onFinish = function onFinish()
+ {
+ closeBrowserWindow();
+ }
+
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById('paragraph-1'))));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,4],
+ getAccessible(doc.getElementById('paragraph-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', CHAR_BOUNDARY, [4,5],
+ getAccessible(doc.getElementById('paragraph-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', CHAR_BOUNDARY, [3,4],
+ getAccessible(doc.getElementById('paragraph-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [5,7],
+ getAccessible(doc.getElementById('paragraph-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,3],
+ getAccessible(doc.getElementById('p1-link-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [10,14],
+ getAccessible(doc.getElementById('paragraph-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,3],
+ getAccessible(doc.getElementById('p1-link-1'), nsIAccessibleText)));
+ // set user input to false, and see if it works
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [5,7],
+ getAccessible(doc.getElementById('paragraph-1'), nsIAccessibleText)),
+ false);
+
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById('section-1'))));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,1],
+ getAccessible(doc.getElementById('section-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,9],
+ getAccessible(doc.getElementById('s1-link-1'), nsIAccessibleText)));
+ // set user input to false, and see if it works
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [10,14],
+ getAccessible(doc.getElementById('s1-link-1'), nsIAccessibleText),
+ false));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [4,6],
+ getAccessible(doc.getElementById('section-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [7,12],
+ getAccessible(doc.getElementById('section-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,2],
+ getAccessible(doc.getElementById('s1-link-2'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [15,19],
+ getAccessible(doc.getElementById('section-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [20,28],
+ getAccessible(doc.getElementById('section-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,5],
+ getAccessible(doc.getElementById('section-2'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [6,10],
+ getAccessible(doc.getElementById('section-2'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,5],
+ getAccessible(doc.getElementById('section-2'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [20,28],
+ getAccessible(doc.getElementById('section-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [15,19],
+ getAccessible(doc.getElementById('section-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,2],
+ getAccessible(doc.getElementById('s1-link-2'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [7,12],
+ getAccessible(doc.getElementById('section-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [4,6],
+ getAccessible(doc.getElementById('section-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [10,14],
+ getAccessible(doc.getElementById('s1-link-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,9],
+ getAccessible(doc.getElementById('s1-link-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,1],
+ getAccessible(doc.getElementById('section-1'), nsIAccessibleText)));
+
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById('s1-link-1'))));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', CHAR_BOUNDARY, [1,2],
+ getAccessible(doc.getElementById('section-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', CHAR_BOUNDARY, [0,1],
+ getAccessible(doc.getElementById('section-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', CHAR_BOUNDARY, [1,2],
+ getAccessible(doc.getElementById('section-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', CHAR_BOUNDARY, [0,1],
+ getAccessible(doc.getElementById('s1-link-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', CHAR_BOUNDARY, [1,2],
+ getAccessible(doc.getElementById('s1-link-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [2,9],
+ getAccessible(doc.getElementById('s1-link-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [10,14],
+ getAccessible(doc.getElementById('s1-link-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', CHAR_BOUNDARY, [3,4],
+ getAccessible(doc.getElementById('section-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', CHAR_BOUNDARY, [13,14],
+ getAccessible(doc.getElementById('s1-link-1'), nsIAccessibleText)));
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById('section-2'))));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', CHAR_BOUNDARY, [27,28],
+ getAccessible(doc.getElementById('section-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', CHAR_BOUNDARY, [0,1],
+ getAccessible(doc.getElementById('section-2'), nsIAccessibleText)));
+
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById('paragraph-2'))));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,12],
+ getAccessible(doc.getElementById('paragraph-2'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,7],
+ getAccessible(doc.getElementById('cell-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,8],
+ getAccessible(doc.getElementById('cell-2'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,3],
+ getAccessible(doc.getElementById('cell-3'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [4,11],
+ getAccessible(doc.getElementById('cell-3'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,6],
+ getAccessible(doc.getElementById('cell-4'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [7,13],
+ getAccessible(doc.getElementById('cell-4'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,7],
+ getAccessible(doc.getElementById('section-3'), nsIAccessibleText)));
+
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById('section-3'))));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,7],
+ getAccessible(doc.getElementById('section-3'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [7,13],
+ getAccessible(doc.getElementById('cell-4'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,6],
+ getAccessible(doc.getElementById('cell-4'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [4,11],
+ getAccessible(doc.getElementById('cell-3'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,3],
+ getAccessible(doc.getElementById('cell-3'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,8],
+ getAccessible(doc.getElementById('cell-2'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,7],
+ getAccessible(doc.getElementById('cell-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,12],
+ getAccessible(doc.getElementById('paragraph-2'), nsIAccessibleText)));
+
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById('paragraph-3'))));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,7],
+ getAccessible(doc.getElementById('paragraph-3'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,8],
+ getAccessible(doc.getElementById('p3-link-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [8,10],
+ getAccessible(doc.getElementById('paragraph-3'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,4],
+ getAccessible(doc.getElementById('p3-link-2'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,5],
+ getAccessible(doc.getElementById('p3-link-3'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [14,20],
+ getAccessible(doc.getElementById('paragraph-3'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,5],
+ getAccessible(doc.getElementById('p3-link-3'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,4],
+ getAccessible(doc.getElementById('p3-link-2'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [8,10],
+ getAccessible(doc.getElementById('paragraph-3'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,8],
+ getAccessible(doc.getElementById('p3-link-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,7],
+ getAccessible(doc.getElementById('paragraph-3'), nsIAccessibleText)));
+
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById('s1-link-2'))));
+ // Start with the pivot in the middle of the paragraph
+ gQueue.push(new setVCPosInvoker(docAcc, "moveNext", ObjectTraversalRule, " will traverse"));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [15,19],
+ getAccessible(doc.getElementById('section-1'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [0,2],
+ getAccessible(doc.getElementById('s1-link-2'), nsIAccessibleText)));
+
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById('end-block'))));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,4],
+ getAccessible(doc.getElementById('end-block'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, null, false));
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById('start-block'))));
+ gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,4],
+ getAccessible(doc.getElementById('start-block'), nsIAccessibleText)));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, null, false));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, null, false));
+ gQueue.push(new setVCPosInvoker(docAcc, null, null,
+ getAccessible(doc.getElementById('start-block'))));
+ gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, null, false));
+
+ gQueue.invoke();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(function () {
+ /* We open a new browser because we need to test with a top-level content
+ document. */
+ openBrowserWindow(
+ doTest,
+ getRootDirectory(window.location.href) + "doc_virtualcursor_text.html");
+ });
+ </script>
+</head>
+<body id="body">
+
+ <a target="_blank"
+ title="Support Movement By Granularity"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=886076">Mozilla Bug 886076</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/relations.js b/accessible/tests/mochitest/relations.js
new file mode 100644
index 000000000..0d9aae2b1
--- /dev/null
+++ b/accessible/tests/mochitest/relations.js
@@ -0,0 +1,192 @@
+////////////////////////////////////////////////////////////////////////////////
+// Constants
+
+var RELATION_CONTROLLED_BY = nsIAccessibleRelation.RELATION_CONTROLLED_BY;
+var RELATION_CONTROLLER_FOR = nsIAccessibleRelation.RELATION_CONTROLLER_FOR;
+var RELATION_DEFAULT_BUTTON = nsIAccessibleRelation.RELATION_DEFAULT_BUTTON;
+var RELATION_DESCRIBED_BY = nsIAccessibleRelation.RELATION_DESCRIBED_BY;
+var RELATION_DESCRIPTION_FOR = nsIAccessibleRelation.RELATION_DESCRIPTION_FOR;
+var RELATION_EMBEDDED_BY = nsIAccessibleRelation.RELATION_EMBEDDED_BY;
+var RELATION_EMBEDS = nsIAccessibleRelation.RELATION_EMBEDS;
+var RELATION_FLOWS_FROM = nsIAccessibleRelation.RELATION_FLOWS_FROM;
+var RELATION_FLOWS_TO = nsIAccessibleRelation.RELATION_FLOWS_TO;
+var RELATION_LABEL_FOR = nsIAccessibleRelation.RELATION_LABEL_FOR;
+var RELATION_LABELLED_BY = nsIAccessibleRelation.RELATION_LABELLED_BY;
+var RELATION_MEMBER_OF = nsIAccessibleRelation.RELATION_MEMBER_OF;
+var RELATION_NODE_CHILD_OF = nsIAccessibleRelation.RELATION_NODE_CHILD_OF;
+var RELATION_NODE_PARENT_OF = nsIAccessibleRelation.RELATION_NODE_PARENT_OF;
+var RELATION_PARENT_WINDOW_OF = nsIAccessibleRelation.RELATION_PARENT_WINDOW_OF;
+var RELATION_POPUP_FOR = nsIAccessibleRelation.RELATION_POPUP_FOR;
+var RELATION_SUBWINDOW_OF = nsIAccessibleRelation.RELATION_SUBWINDOW_OF;
+var RELATION_CONTAINING_DOCUMENT = nsIAccessibleRelation.RELATION_CONTAINING_DOCUMENT;
+var RELATION_CONTAINING_TAB_PANE = nsIAccessibleRelation.RELATION_CONTAINING_TAB_PANE;
+var RELATION_CONTAINING_APPLICATION = nsIAccessibleRelation.RELATION_CONTAINING_APPLICATION;
+const RELATION_DETAILS = nsIAccessibleRelation.RELATION_DETAILS;
+const RELATION_DETAILS_FOR = nsIAccessibleRelation.RELATION_DETAILS_FOR;
+const RELATION_ERRORMSG = nsIAccessibleRelation.RELATION_ERRORMSG;
+const RELATION_ERRORMSG_FOR = nsIAccessibleRelation.RELATION_ERRORMSG_FOR;
+
+////////////////////////////////////////////////////////////////////////////////
+// General
+
+/**
+ * Test the accessible relation.
+ *
+ * @param aIdentifier [in] identifier to get an accessible, may be ID
+ * attribute or DOM element or accessible object
+ * @param aRelType [in] relation type (see constants above)
+ * @param aRelatedIdentifiers [in] identifier or array of identifiers of
+ * expected related accessibles
+ */
+function testRelation(aIdentifier, aRelType, aRelatedIdentifiers)
+{
+ var relation = getRelationByType(aIdentifier, aRelType);
+
+ var relDescr = getRelationErrorMsg(aIdentifier, aRelType);
+ var relDescrStart = getRelationErrorMsg(aIdentifier, aRelType, true);
+
+ if (!relation || !relation.targetsCount) {
+ if (!aRelatedIdentifiers) {
+ ok(true, "No" + relDescr);
+ return;
+ }
+
+ var msg = relDescrStart + "has no expected targets: '" +
+ prettyName(aRelatedIdentifiers) + "'";
+
+ ok(false, msg);
+ return;
+
+ } else if (!aRelatedIdentifiers) {
+ ok(false, "There are unexpected targets of " + relDescr);
+ return;
+ }
+
+ var relatedIds = (aRelatedIdentifiers instanceof Array) ?
+ aRelatedIdentifiers : [aRelatedIdentifiers];
+
+ var targets = [];
+ for (var idx = 0; idx < relatedIds.length; idx++)
+ targets.push(getAccessible(relatedIds[idx]));
+
+ if (targets.length != relatedIds.length)
+ return;
+
+ var actualTargets = relation.getTargets();
+
+ // Check if all given related accessibles are targets of obtained relation.
+ for (var idx = 0; idx < targets.length; idx++) {
+ var isFound = false;
+ var enumerate = actualTargets.enumerate();
+ while (enumerate.hasMoreElements()) {
+ var relatedAcc = enumerate.getNext().QueryInterface(nsIAccessible);
+ if (targets[idx] == relatedAcc) {
+ isFound = true;
+ break;
+ }
+ }
+
+ ok(isFound, prettyName(relatedIds[idx]) + " is not a target of" + relDescr);
+ }
+
+ // Check if all obtained targets are given related accessibles.
+ var enumerate = actualTargets.enumerate();
+ while (enumerate.hasMoreElements()) {
+ var relatedAcc = enumerate.getNext().QueryInterface(nsIAccessible);
+ for (var idx = 0; idx < targets.length && relatedAcc != targets[idx]; idx++);
+
+ if (idx == targets.length)
+ ok(false, "There is unexpected target" + prettyName(relatedAcc) + "of" + relDescr);
+ }
+}
+
+/**
+ * Test that the given accessible relations don't exist.
+ *
+ * @param aIdentifier [in] identifier to get an accessible, may be ID
+ * attribute or DOM element or accessible object
+ * @param aRelType [in] relation type (see constants above)
+ * @param aUnrelatedIdentifiers [in] identifier or array of identifiers of
+ * accessibles that shouldn't exist for this
+ * relation.
+ */
+function testAbsentRelation(aIdentifier, aRelType, aUnrelatedIdentifiers)
+{
+ var relation = getRelationByType(aIdentifier, aRelType);
+
+ var relDescr = getRelationErrorMsg(aIdentifier, aRelType);
+ var relDescrStart = getRelationErrorMsg(aIdentifier, aRelType, true);
+
+ if (!aUnrelatedIdentifiers) {
+ ok(false, "No identifiers given for unrelated accessibles.");
+ return;
+ }
+
+ if (!relation || !relation.targetsCount) {
+ ok(true, "No relations exist.");
+ return;
+ }
+
+ var relatedIds = (aUnrelatedIdentifiers instanceof Array) ?
+ aUnrelatedIdentifiers : [aUnrelatedIdentifiers];
+
+ var targets = [];
+ for (var idx = 0; idx < relatedIds.length; idx++)
+ targets.push(getAccessible(relatedIds[idx]));
+
+ if (targets.length != relatedIds.length)
+ return;
+
+ var actualTargets = relation.getTargets();
+
+ // Any found targets that match given accessibles should be called out.
+ for (var idx = 0; idx < targets.length; idx++) {
+ var notFound = true;
+ var enumerate = actualTargets.enumerate();
+ while (enumerate.hasMoreElements()) {
+ var relatedAcc = enumerate.getNext().QueryInterface(nsIAccessible);
+ if (targets[idx] == relatedAcc) {
+ notFound = false;
+ break;
+ }
+ }
+
+ ok(notFound, prettyName(relatedIds[idx]) + " is a target of " + relDescr);
+ }
+}
+
+/**
+ * Return related accessible for the given relation type.
+ *
+ * @param aIdentifier [in] identifier to get an accessible, may be ID attribute
+ * or DOM element or accessible object
+ * @param aRelType [in] relation type (see constants above)
+ */
+function getRelationByType(aIdentifier, aRelType)
+{
+ var acc = getAccessible(aIdentifier);
+ if (!acc)
+ return;
+
+ var relation = null;
+ try {
+ relation = acc.getRelationByType(aRelType);
+ } catch (e) {
+ ok(false, "Can't get" + getRelationErrorMsg(aIdentifier, aRelType));
+ }
+
+ return relation;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Private implementation details
+
+function getRelationErrorMsg(aIdentifier, aRelType, aIsStartSentence)
+{
+ var relStr = relationTypeToString(aRelType);
+ var msg = aIsStartSentence ? "Relation of '" : " relation of '";
+ msg += relStr + "' type for '" + prettyName(aIdentifier) + "'";
+ msg += aIsStartSentence ? " " : ".";
+
+ return msg;
+}
diff --git a/accessible/tests/mochitest/relations/a11y.ini b/accessible/tests/mochitest/relations/a11y.ini
new file mode 100644
index 000000000..a2da0cf2e
--- /dev/null
+++ b/accessible/tests/mochitest/relations/a11y.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+
+[test_bindings.xhtml]
+[test_embeds.xul]
+[test_general.html]
+[test_general.xul]
+[test_tabbrowser.xul]
+[test_tree.xul]
+[test_ui_modalprompt.html]
+[test_update.html]
diff --git a/accessible/tests/mochitest/relations/test_bindings.xhtml b/accessible/tests/mochitest/relations/test_bindings.xhtml
new file mode 100644
index 000000000..65a7a0875
--- /dev/null
+++ b/accessible/tests/mochitest/relations/test_bindings.xhtml
@@ -0,0 +1,103 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+ <title>Accessible relations for bindings</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <style>
+ .button {
+ -moz-binding: url('#custombutton');
+ }
+
+ .button2 {
+ -moz-binding: url('#custombutton2');
+ }
+ </style>
+
+ <bindings xmlns="http://www.mozilla.org/xbl">
+ <binding id="custombutton">
+ <content aria-labelledby="button.label label">
+ <label xmlns="http://www.w3.org/1999/xhtml" anonid="button.label">
+ anon label
+ </label>
+ <button xmlns="http://www.w3.org/1999/xhtml" anonid="button.button"
+ aria-labelledby="button.label label">
+ a button
+ </button>
+ <div xmlns="http://www.w3.org/1999/xhtml"
+ anonid="button.button2" class="button2"
+ aria-labelledby="button.label"></div>
+ <div xmlns="http://www.w3.org/1999/xhtml"
+ anonid="button.button3" class="button2"></div>
+ </content>
+ </binding>
+ <binding id="custombutton2">
+ <content aria-labelledby="button2.label">
+ <label xmlns="http://www.w3.org/1999/xhtml" anonid="button2.label">
+ nested anon label
+ </label>
+ </content>
+ </binding>
+ </bindings>
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../relations.js"></script>
+
+ <script type="application/javascript">
+ function doTests()
+ {
+ var button = document.getElementById("button");
+ var anonLabel = document.
+ getAnonymousElementByAttribute(button, "anonid", "button.label");
+ var anonButton = document.
+ getAnonymousElementByAttribute(button, "anonid", "button.button");
+ var anonButton2 = document.
+ getAnonymousElementByAttribute(button, "anonid", "button.button2");
+ var anonButton3 = document.
+ getAnonymousElementByAttribute(button, "anonid", "button.button3");
+ var anonAnonLabel = document.
+ getAnonymousElementByAttribute(anonButton3, "anonid", "button2.label");
+
+ testRelation("label", RELATION_LABEL_FOR, button);
+ testRelation(anonLabel, RELATION_LABEL_FOR, [button, anonButton, anonButton2]);
+ testRelation(button, RELATION_LABELLED_BY, [anonLabel, "label"]);
+ testRelation(anonButton, RELATION_LABELLED_BY, anonLabel);
+ testRelation(anonButton2, RELATION_LABELLED_BY, anonLabel);
+ testRelation(anonButton3, RELATION_LABELLED_BY, anonAnonLabel);
+ testRelation(anonAnonLabel, RELATION_LABEL_FOR, anonButton3);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=421242"
+ title="Allow relations in anonymous content for binding parent">
+ Mozilla Bug 421242
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <div id="eventdump"></div>
+
+ <label id="label">explicit label</label>
+ <div id="button" class="button"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/relations/test_embeds.xul b/accessible/tests/mochitest/relations/test_embeds.xul
new file mode 100644
index 000000000..0cb6d6c65
--- /dev/null
+++ b/accessible/tests/mochitest/relations/test_embeds.xul
@@ -0,0 +1,122 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Embeds relation tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../relations.js"></script>
+ <script type="application/javascript"
+ src="../browser.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function loadURI(aURI)
+ {
+ this.invoke = function loadURI_invoke()
+ {
+ tabBrowser().loadURI(aURI);
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument)
+ ];
+
+ this.finalCheck = function loadURI_finalCheck()
+ {
+ testRelation(browserDocument(), RELATION_EMBEDS,
+ getAccessible(currentTabDocument()));
+ }
+
+ this.getID = function loadURI_getID()
+ {
+ return "load uri " + aURI;
+ }
+ }
+
+ function loadOneTab(aURI)
+ {
+ this.invoke = function loadOneTab_invoke()
+ {
+ tabBrowser().loadOneTab(aURI, null, null, null, false);
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument)
+ ];
+
+ this.finalCheck = function loadURI_finalCheck()
+ {
+ testRelation(browserDocument(), RELATION_EMBEDS,
+ getAccessible(currentTabDocument()));
+ }
+
+ this.getID = function loadOneTab_getID()
+ {
+ return "load uri '" + aURI + "' in new tab";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Testing
+
+ //gA11yEventDumpToConsole = true; // debug
+
+ var gQueue = null;
+ function doTests()
+ {
+ testRelation(browserDocument(), RELATION_EMBEDS,
+ getAccessible(currentTabDocument()));
+
+ enableLogging("docload");
+ gQueue = new eventQueue();
+
+ gQueue.push(new loadURI("about:about"));
+ gQueue.push(new loadOneTab("about:mozilla"));
+
+ gQueue.onFinish = function()
+ {
+ disableLogging();
+ closeBrowserWindow();
+ }
+ gQueue.invoke();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ openBrowserWindow(doTests, "about:");
+ ]]>
+ </script>
+
+ <vbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=707654"
+ title="Embeds relation on root accessible can return not content document">
+ Mozilla Bug 707654
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+ </vbox>
+</window>
diff --git a/accessible/tests/mochitest/relations/test_general.html b/accessible/tests/mochitest/relations/test_general.html
new file mode 100644
index 000000000..bcb1d282a
--- /dev/null
+++ b/accessible/tests/mochitest/relations/test_general.html
@@ -0,0 +1,406 @@
+<html>
+
+<head>
+ <title>nsIAccessible::getAccessibleRelated() tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../relations.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ // html:label@for
+ testRelation("label1_1", RELATION_LABEL_FOR, "control1_1");
+ testRelation("control1_1", RELATION_LABELLED_BY, "label1_1");
+
+ // html:label@for, multiple
+ testRelation("label1_2", RELATION_LABEL_FOR, "control1_2");
+ testRelation("label1_3", RELATION_LABEL_FOR, "control1_2");
+ testRelation("control1_2", RELATION_LABELLED_BY,
+ [ "label1_2", "label1_3" ]);
+
+ // ancestor html:label (implicit association)
+ testRelation("label1_4", RELATION_LABEL_FOR, "control1_4");
+ testRelation("control1_4", RELATION_LABELLED_BY, "label1_4");
+ testRelation("control1_4_option1", RELATION_LABELLED_BY, null);
+ testRelation("label1_5", RELATION_LABEL_FOR, "control1_5");
+ testRelation("control1_5", RELATION_LABELLED_BY, "label1_5");
+ testRelation("label1_6", RELATION_LABEL_FOR, "control1_6");
+ testRelation("control1_6", RELATION_LABELLED_BY, "label1_6");
+ testRelation("label1_7", RELATION_LABEL_FOR, "control1_7");
+ testRelation("control1_7", RELATION_LABELLED_BY, "label1_7");
+ testRelation("label1_8", RELATION_LABEL_FOR, "control1_8");
+ testRelation("control1_8", RELATION_LABELLED_BY, "label1_8");
+ testRelation("label1_9", RELATION_LABEL_FOR, "control1_9");
+ testRelation("control1_9", RELATION_LABELLED_BY, "label1_9");
+ testRelation("label1_10", RELATION_LABEL_FOR, "control1_10");
+ testRelation("control1_10", RELATION_LABELLED_BY, "label1_10");
+ testRelation("label1_11", RELATION_LABEL_FOR, "control1_11");
+ testRelation("control1_11", RELATION_LABELLED_BY, "label1_11");
+ testRelation("label1_12", RELATION_LABEL_FOR, "control1_12");
+ testRelation("control1_12", RELATION_LABELLED_BY, "label1_12");
+
+ testRelation("label1_13", RELATION_LABEL_FOR, null);
+ testRelation("control1_13", RELATION_LABELLED_BY, null);
+ testRelation("control1_14", RELATION_LABELLED_BY, "label1_14");
+
+ // aria-labelledby
+ testRelation("label2", RELATION_LABEL_FOR, "checkbox2");
+ testRelation("checkbox2", RELATION_LABELLED_BY, "label2");
+
+ // aria-labelledby, multiple relations
+ testRelation("label3", RELATION_LABEL_FOR, "checkbox3");
+ testRelation("label4", RELATION_LABEL_FOR, "checkbox3");
+ testRelation("checkbox3", RELATION_LABELLED_BY, ["label3", "label4"]);
+
+ // aria-describedby
+ testRelation("descr1", RELATION_DESCRIPTION_FOR, "checkbox4");
+ testRelation("checkbox4", RELATION_DESCRIBED_BY, "descr1");
+
+ // aria-describedby, multiple relations
+ testRelation("descr2", RELATION_DESCRIPTION_FOR, "checkbox5");
+ testRelation("descr3", RELATION_DESCRIPTION_FOR, "checkbox5");
+ testRelation("checkbox5", RELATION_DESCRIBED_BY, ["descr2", "descr3"]);
+
+ // aria_owns, multiple relations
+ testRelation("treeitem1", RELATION_NODE_CHILD_OF, "tree");
+ testRelation("treeitem2", RELATION_NODE_CHILD_OF, "tree");
+
+ // 'node child of' relation for outlineitem role
+ testRelation("treeitem3", RELATION_NODE_CHILD_OF, "tree");
+ testRelation("treeitem4", RELATION_NODE_CHILD_OF, "tree");
+ testRelation("treeitem5", RELATION_NODE_CHILD_OF, "treeitem4");
+ testRelation("treeitem6", RELATION_NODE_CHILD_OF, "tree");
+ testRelation("treeitem7", RELATION_NODE_CHILD_OF, "treeitem6");
+ testRelation("tree2_ti1", RELATION_NODE_CHILD_OF, "tree2");
+ testRelation("tree2_ti1a", RELATION_NODE_CHILD_OF, "tree2_ti1");
+ testRelation("tree2_ti1b", RELATION_NODE_CHILD_OF, "tree2_ti1");
+
+ // 'node child of' relation for row role in grid.
+ // Relation for row associated using aria-level should exist.
+ testRelation("simplegrid-row3", RELATION_NODE_CHILD_OF,
+ "simplegrid-row2");
+ // Relations for hierarchical children elements shouldn't exist.
+ testAbsentRelation("simplegrid-row1", RELATION_NODE_CHILD_OF,
+ "simplegrid");
+ testAbsentRelation("simplegrid-row2", RELATION_NODE_CHILD_OF,
+ "simplegrid");
+
+ // 'node child of' relation for row role of treegrid
+ testRelation("treegridrow1", RELATION_NODE_CHILD_OF, "treegrid");
+ testRelation("treegridrow2", RELATION_NODE_CHILD_OF, "treegrid");
+ testRelation("treegridrow3", RELATION_NODE_CHILD_OF, "treegridrow2");
+
+ // 'node child of' relation for lists organized by groups
+ testRelation("listitem1", RELATION_NODE_CHILD_OF, "list");
+ testRelation("listitem1.1", RELATION_NODE_CHILD_OF, "listitem1");
+ testRelation("listitem1.2", RELATION_NODE_CHILD_OF, "listitem1");
+
+ // 'node child of' relation for the document having window, returns
+ // direct accessible parent (fixed in bug 419770).
+ var iframeElmObj = {};
+ var iframeAcc = getAccessible("iframe", null, iframeElmObj);
+ var iframeDoc = iframeElmObj.value.contentDocument;
+ var iframeDocAcc = getAccessible(iframeDoc);
+ testRelation(iframeDocAcc, RELATION_NODE_CHILD_OF, iframeAcc);
+
+ // 'node parent of' relation on ARIA tree and treegrid.
+ testRelation("tree", RELATION_NODE_PARENT_OF,
+ ["treeitem1", "treeitem2", // aria-owns
+ "treeitem3", "treeitem4", "treeitem6"]); // children
+ testRelation("treeitem4", RELATION_NODE_PARENT_OF,
+ "treeitem5"); // aria-level
+ testRelation("treeitem6", RELATION_NODE_PARENT_OF,
+ "treeitem7"); // // group role
+ testRelation("tree2", RELATION_NODE_PARENT_OF, "tree2_ti1"); // group role
+ testRelation("tree2_ti1", RELATION_NODE_PARENT_OF,
+ ["tree2_ti1a", "tree2_ti1b"]); // group role
+
+ testRelation("treegridrow2", RELATION_NODE_PARENT_OF, "treegridrow3");
+ testRelation("treegrid", RELATION_NODE_PARENT_OF,
+ ["treegridrow1", "treegridrow2"]);
+
+ // 'node parent of' relation on ARIA grid.
+ // 'node parent of' relation on ARIA grid's row.
+ // Should only have relation to child through aria-level.
+ testRelation("simplegrid-row2", RELATION_NODE_PARENT_OF,
+ "simplegrid-row3");
+
+ // 'node parent of' relation on ARIA list structured by groups
+ testRelation("list", RELATION_NODE_PARENT_OF,
+ "listitem1");
+ testRelation("listitem1", RELATION_NODE_PARENT_OF,
+ [ "listitem1.1", "listitem1.2" ]);
+
+ // aria-atomic
+ testRelation(getNode("atomic").firstChild, RELATION_MEMBER_OF, "atomic");
+
+ // aria-controls
+ getAccessible("tab");
+ todo(false,
+ "Getting an accessible tab, otherwise relations for tabpanel aren't cached. Bug 606924 will fix that.");
+ testRelation("tabpanel", RELATION_CONTROLLED_BY, "tab");
+ testRelation("tab", RELATION_CONTROLLER_FOR, "tabpanel");
+
+ // aria-controls, multiple relations
+ testRelation("lr1", RELATION_CONTROLLED_BY, "button");
+ testRelation("lr2", RELATION_CONTROLLED_BY, "button");
+ testRelation("button", RELATION_CONTROLLER_FOR, ["lr1", "lr2"]);
+
+ // aria-flowto
+ testRelation("flowto", RELATION_FLOWS_TO, "flowfrom");
+ testRelation("flowfrom", RELATION_FLOWS_FROM, "flowto");
+
+ // aria-flowto, multiple relations
+ testRelation("flowto1", RELATION_FLOWS_TO, ["flowfrom1", "flowfrom2"]);
+ testRelation("flowfrom1", RELATION_FLOWS_FROM, "flowto1");
+ testRelation("flowfrom2", RELATION_FLOWS_FROM, "flowto1");
+
+ // 'default button' relation
+ testRelation("input", RELATION_DEFAULT_BUTTON, "submit");
+
+ // output 'for' relations
+ testRelation("output", RELATION_CONTROLLED_BY, ["input", "input2"]);
+ testRelation("output2", RELATION_CONTROLLED_BY, ["input", "input2"]);
+ testRelation("input", RELATION_CONTROLLER_FOR, ["output", "output2"]);
+ testRelation("input2", RELATION_CONTROLLER_FOR, ["output", "output2"]);
+
+ // 'described by'/'description for' relation for html:table and
+ // html:caption
+ testRelation("caption", RELATION_LABEL_FOR, "table");
+ testRelation("table", RELATION_LABELLED_BY, "caption");
+
+ // 'labelled by'/'label for' relation for html:fieldset and
+ // html:legend
+ testRelation("legend", RELATION_LABEL_FOR, "fieldset");
+ testRelation("fieldset", RELATION_LABELLED_BY, "legend");
+
+ // containing relations
+ testRelation("control1_1", RELATION_CONTAINING_DOCUMENT, document);
+ testRelation("control1_1", RELATION_CONTAINING_TAB_PANE, getTabDocAccessible("control1_1"));
+ testRelation("control1_1", RELATION_CONTAINING_APPLICATION, getApplicationAccessible());
+
+ // details
+ testRelation("has_details", RELATION_DETAILS, "details");
+ testRelation("details", RELATION_DETAILS_FOR, "has_details");
+
+ // error
+ testRelation("has_error", RELATION_ERRORMSG, "error");
+ testRelation("error", RELATION_ERRORMSG_FOR, "has_error");
+
+ // finish test
+ SimpleTest.finish();
+ }
+
+ disableLogging(); // from test_embeds.xul
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=475298"
+ title="mochitests for accessible relations">
+ Bug 475298
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=527461"
+ title="Implement RELATION_NODE_PARENT_OF">
+ Bug 527461
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=558036"
+ title="make HTML <output> accessible">
+ Bug 558036
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=682790"
+ title="Ignore implicit label association when it's associated explicitly">
+ Bug 682790
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=687393"
+ title="HTML select options gets relation from containing label">
+ Bug 687393
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=864224"
+ title="Support nested ARIA listitems structured by role='group'">
+ Bug 864224
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <label id="label1_1" for="control1_1">label</label>
+ <input id="control1_1">
+
+ <label id="label1_2" for="control1_2">label</label>
+ <label id="label1_3" for="control1_2">label</label>
+ <input id="control1_2">
+
+ <label id="label1_4">Label
+ <select id="control1_4">
+ <option id="control1_4_option1">option</option>
+ </select>
+ </label>
+ <label id="label1_5">Label
+ <button id="control1_5">button</button>
+ </label>
+ <label id="label1_6">Label
+ <input id="control1_6">
+ </label>
+ <label id="label1_7">Label
+ <input id="control1_7" type="checkbox">
+ </label>
+ <label id="label1_8">Label
+ <input id="control1_8" type="radio">
+ </label>
+ <label id="label1_9">Label
+ <input id="control1_9" type="button" value="button">
+ </label>
+ <label id="label1_10">Label
+ <input id="control1_10" type="submit">
+ </label>
+ <label id="label1_11">Label
+ <input id="control1_11" type="image">
+ </label>
+ <label id="label1_12">Label
+ <progress id="control1_12"></progress>
+ </label>
+
+ <label id="label1_13" for="">Label
+ <input id="control1_13">
+ </label>
+ <label id="label1_14" for="control1_14">Label
+ <input id="control1_14">
+ </label>
+
+ <span id="label2">label</span>
+ <span role="checkbox" id="checkbox2" aria-labelledby="label2"></span>
+
+ <span id="label3">label1</span>
+ <span id="label4">label2</span>
+ <span role="checkbox" id="checkbox3" aria-labelledby="label3 label4"></span>
+
+ <span id="descr1">description</span>
+ <span role="checkbox" id="checkbox4" aria-describedby="descr1"></span>
+
+ <span id="descr2">description1</span>
+ <span id="descr3">description2</span>
+ <span role="checkbox" id="checkbox5" aria-describedby="descr2 descr3"></span>
+
+ <div role="treeitem" id="treeitem1">Yellow</div>
+ <div role="treeitem" id="treeitem2">Orange</div>
+ <div id="tree" role="tree" aria-owns="treeitem1 treeitem2">
+ <div role="treeitem" id="treeitem3">Blue</div>
+ <div role="treeitem" id="treeitem4" aria-level="1">Green</div>
+ <div role="treeitem" id="treeitem5" aria-level="2">Light green</div>
+ <div role="treeitem" id="treeitem6" aria-level="1">Green2</div>
+ <div role="group">
+ <div role="treeitem" id="treeitem7">Super light green</div>
+ </div>
+ </div>
+
+ <div role="grid" id="simplegrid">
+ <div role="row" id="simplegrid-row1" aria-level="1">
+ <div role="gridcell">cell 1,1</div>
+ <div role="gridcell">cell 1,2</div>
+ </div>
+ <div role="row" id="simplegrid-row2" aria-level="1">
+ <div role="gridcell">cell 2,1</div>
+ <div role="gridcell">cell 2,2</div>
+ </div>
+ <div role="row" id="simplegrid-row3" aria-level="2">
+ <div role="gridcell">cell 3,1</div>
+ <div role="gridcell">cell 3,2</div>
+ </div>
+ </div>
+
+ <ul role="tree" id="tree2">
+ <li role="treeitem" id="tree2_ti1">Item 1
+ <ul role="group">
+ <li role="treeitem" id="tree2_ti1a">Item 1A</li>
+ <li role="treeitem" id="tree2_ti1b">Item 1B</li>
+ </ul>
+ </li>
+ </ul>
+
+ <div role="treegrid" id="treegrid">
+ <div role="row" id="treegridrow1">
+ <span role="gridcell">cell1</span><span role="gridcell">cell2</span>
+ </div>
+ <div role="row" id="treegridrow2" aria-level="1">
+ <span role="gridcell">cell3</span><span role="gridcell">cell4</span>
+ </div>
+ <div role="row" id="treegridrow3" aria-level="2">
+ <span role="gridcell">cell5</span><span role="gridcell">cell6</span>
+ </div>
+ </div>
+
+ <div role="list" id="list">
+ <div role="listitem" id="listitem1">Item 1
+ <div role="group">
+ <div role="listitem" id="listitem1.1">Item 1A</div>
+ <div role="listitem" id="listitem1.2">Item 1B</div>
+ </div>
+ </div>
+ </div>
+
+ <iframe id="iframe"></iframe>
+
+ <div id="tablist" role="tablist">
+ <div id="tab" role="tab" aria-controls="tabpanel">tab</div>
+ </div>
+ <div id="tabpanel" role="tabpanel">tabpanel</div>
+
+ <div id="lr1" aria-live="assertive">1</div>
+ <div id="lr2" aria-live="assertive">a</div>
+ <input type="button" id="button" aria-controls="lr1 lr2"
+ onclick="getNode('lr1').textContent += '1'; getNode('lr2').textContent += 'a';"/>
+
+ <div id="atomic" aria-atomic="true">live region</div>
+
+ <span id="flowto" aria-flowto="flowfrom">flow to</span>
+ <span id="flowfrom">flow from</span>
+
+ <span id="flowto1" aria-flowto="flowfrom1 flowfrom2">flow to</span>
+ <span id="flowfrom1">flow from</span>
+ <span id="flowfrom2">flow from</span>
+
+ <form id="form">
+ <input id="input" />
+ <input id="input2" />
+ <input type="submit" id="submit" />
+ <output id="output" style="display:block" for="input input2"></output>
+ <output id="output2" for="input input2"></output>
+ </form>
+
+ <table id="table">
+ <caption id="caption">tabple caption</caption>
+ <tr>
+ <td>cell1</td><td>cell2</td>
+ </tr>
+ </table>
+
+ <fieldset id="fieldset">
+ <legend id="legend">legend</legend>
+ <input />
+ </fieldset>
+
+ <input id="has_details" aria-details="details"><section id="details"></section>
+ <input id="has_error" aria-errormessage="error"><section id="error"></section>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/relations/test_general.xul b/accessible/tests/mochitest/relations/test_general.xul
new file mode 100644
index 000000000..23bf7276b
--- /dev/null
+++ b/accessible/tests/mochitest/relations/test_general.xul
@@ -0,0 +1,238 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="nsIAccessible::getAccessibleRelated() tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../relations.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function doTest()
+ {
+ // xul:label@control
+ testRelation("label1", RELATION_LABEL_FOR, "checkbox1");
+ testRelation("checkbox1", RELATION_LABELLED_BY, "label1");
+
+ // xul:label@control, multiple
+ testRelation("label1_1", RELATION_LABEL_FOR, "checkbox1_1");
+ testRelation("label1_2", RELATION_LABEL_FOR, "checkbox1_1");
+ testRelation("checkbox1_1", RELATION_LABELLED_BY,
+ [ "label1_1", "label1_2" ]);
+
+ // aria-labelledby
+ testRelation("label2", RELATION_LABEL_FOR, "checkbox2");
+ testRelation("checkbox2", RELATION_LABELLED_BY, "label2");
+
+ // aria-labelledby, multiple relations
+ testRelation("label3", RELATION_LABEL_FOR, "checkbox3");
+ testRelation("label4", RELATION_LABEL_FOR, "checkbox3");
+ testRelation("checkbox3", RELATION_LABELLED_BY, ["label3", "label4"]);
+
+ // aria-describedby
+ testRelation("descr1", RELATION_DESCRIPTION_FOR, "checkbox4");
+ testRelation("checkbox4", RELATION_DESCRIBED_BY, "descr1");
+
+ // aria-describedby, multiple relations
+ testRelation("descr2", RELATION_DESCRIPTION_FOR, "checkbox5");
+ testRelation("descr3", RELATION_DESCRIPTION_FOR, "checkbox5");
+ testRelation("checkbox5", RELATION_DESCRIBED_BY, ["descr2", "descr3"]);
+
+ // xul:description@control
+ testRelation("descr4", RELATION_DESCRIPTION_FOR, "checkbox6");
+ testRelation("checkbox6", RELATION_DESCRIBED_BY, "descr4");
+
+ // xul:description@control, multiple
+ testRelation("descr5", RELATION_DESCRIPTION_FOR, "checkbox7");
+ testRelation("descr6", RELATION_DESCRIPTION_FOR, "checkbox7");
+ testRelation("checkbox7", RELATION_DESCRIBED_BY,
+ [ "descr5", "descr6" ]);
+
+ // aria_owns, multiple relations
+ testRelation("treeitem1", RELATION_NODE_CHILD_OF, "tree");
+ testRelation("treeitem2", RELATION_NODE_CHILD_OF, "tree");
+
+ // 'node child of' relation for outlineitem role
+ testRelation("treeitem3", RELATION_NODE_CHILD_OF, "tree");
+ testRelation("treeitem4", RELATION_NODE_CHILD_OF, "tree");
+ testRelation("treeitem5", RELATION_NODE_CHILD_OF, "treeitem4");
+
+ // no relation node_child_of for accessible contained in an unexpected
+ // parent
+ testRelation("treeitem6", RELATION_NODE_CHILD_OF, null);
+
+ // 'node child of' relation for the document having window, returns
+ // direct accessible parent (fixed in bug 419770).
+ var iframeElmObj = {};
+ var iframeAcc = getAccessible("iframe", null, iframeElmObj);
+ var iframeDoc = iframeElmObj.value.contentDocument;
+ var iframeDocAcc = getAccessible(iframeDoc);
+ testRelation(iframeDocAcc, RELATION_NODE_CHILD_OF, iframeAcc);
+
+ // aria-controls
+ getAccessible("tab");
+ todo(false,
+ "Getting an accessible tab, otherwise relations for tabpanel aren't cached. Bug 606924 will fix that.");
+ testRelation("tabpanel", RELATION_CONTROLLED_BY, "tab");
+ testRelation("tab", RELATION_CONTROLLER_FOR, "tabpanel");
+
+ // aria-controls, multiple relations
+ testRelation("lr1", RELATION_CONTROLLED_BY, "button");
+ testRelation("lr2", RELATION_CONTROLLED_BY, "button");
+ testRelation("button", RELATION_CONTROLLER_FOR, ["lr1", "lr2"]);
+
+ // aria-flowto
+ testRelation("flowto", RELATION_FLOWS_TO, "flowfrom");
+ testRelation("flowfrom", RELATION_FLOWS_FROM, "flowto");
+
+ // aria-flowto, multiple relations
+ testRelation("flowto1", RELATION_FLOWS_TO, ["flowfrom1", "flowfrom2"]);
+ testRelation("flowfrom1", RELATION_FLOWS_FROM, "flowto1");
+ testRelation("flowfrom2", RELATION_FLOWS_FROM, "flowto1");
+
+ // 'default button' relation
+ testRelation("textbox", RELATION_DEFAULT_BUTTON, "submit");
+
+ // 'labelled by'/'label for' relation for xul:goupbox and xul:label of
+ // xul:caption
+ var groupboxAcc = getAccessible("groupbox");
+ var labelAcc = groupboxAcc.firstChild;
+ testRelation(labelAcc, RELATION_LABEL_FOR, groupboxAcc);
+ testRelation(groupboxAcc, RELATION_LABELLED_BY, labelAcc);
+
+ // 'labelled by'/'label for' relations for xul:tab and xul:tabpanel
+ // (fixed in bug 366527)
+ testRelation("tabpanel1", RELATION_LABELLED_BY, "tab1");
+ testRelation("tab1", RELATION_LABEL_FOR, "tabpanel1");
+ testRelation("tabpanel2", RELATION_LABELLED_BY, "tab2");
+ testRelation("tab2", RELATION_LABEL_FOR, "tabpanel2");
+ testRelation("tabpanel3", RELATION_LABELLED_BY, "tab3");
+ testRelation("tab3", RELATION_LABEL_FOR, "tabpanel3");
+
+ // finish test
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <vbox style="overflow: auto;" flex="1">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=475298"
+ title="mochitests for accessible relations">
+ Mozilla Bug 475298
+ </a><br/>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=673389"
+ title="node_child_of on an item not in a proper container">
+ Mozilla Bug 67389
+ </a><br/>
+
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <label id="label1" control="checkbox1">label</label>
+ <checkbox id="checkbox1"/>
+
+ <label id="label1_1" control="checkbox1_1">label</label>
+ <label id="label1_2" control="checkbox1_1">label</label>
+ <checkbox id="checkbox1_1"/>
+
+ <description id="label2">label</description>
+ <description role="checkbox" id="checkbox2" aria-labelledby="label2"/>
+
+ <description id="label3">label</description>
+ <description id="label4">label</description>
+ <description role="checkbox" id="checkbox3"
+ aria-labelledby="label3 label4"/>
+
+ <description id="descr1">description</description>
+ <description role="checkbox" id="checkbox4" aria-describedby="descr1"/>
+
+ <description id="descr2">label</description>
+ <description id="descr3">label</description>
+ <description role="checkbox" id="checkbox5"
+ aria-describedby="descr2 descr3"/>
+
+ <description id="descr4" control="checkbox6">description</description>
+ <checkbox id="checkbox6"/>
+
+ <description id="descr5" control="checkbox7">description</description>
+ <description id="descr6" control="checkbox7">description</description>
+ <checkbox id="checkbox7"/>
+
+ <description role="treeitem" id="treeitem1">Yellow</description>
+ <description role="treeitem" id="treeitem2">Orange</description>
+ <vbox id="tree" role="tree" aria-owns="treeitem1 treeitem2">
+ <description role="treeitem" id="treeitem3">Blue</description>
+ <description role="treeitem" id="treeitem4" aria-level="1">Green</description>
+ <description role="treeitem" id="treeitem5" aria-level="2">Light green</description>
+ </vbox>
+
+ <description role="treeitem" id="treeitem6">Dark green</description>
+
+ <iframe id="iframe"/>
+
+ <hbox id="tablist" role="tablist">
+ <description id="tab" role="tab" aria-controls="tabpanel">tab</description>
+ </hbox>
+ <description id="tabpanel" role="tabpanel">tabpanel</description>
+
+ <description id="lr1" aria-live="assertive">1</description>
+ <description id="lr2" aria-live="assertive">a</description>
+ <button id="button" aria-controls="lr1 lr2" label="button"
+ oncommand="getNode('lr1').textContent += '1'; getNode('lr2').textContent += 'a';"/>
+
+ <description id="flowto1" aria-flowto="flowfrom1 flowfrom2">flow to</description>
+ <description id="flowfrom1">flow from</description>
+ <description id="flowfrom2">flow from</description>
+
+ <description id="flowto" aria-flowto="flowfrom">flow to</description>
+ <description id="flowfrom">flow from</description>
+
+ <textbox id="textbox"/>
+ <button id="submit" default="true" label="Default"/>
+
+ <groupbox id="groupbox">
+ <caption label="caption"/>
+ </groupbox>
+
+ <tabbox>
+ <tabs>
+ <tab label="tab1" id="tab1"/>
+ <tab label="tab2" id="tab2" linkedpanel="tabpanel2"/>
+ <tab label="tab3" id="tab3" linkedpanel="tabpanel3"/>
+ </tabs>
+ <tabpanels>
+ <tabpanel id="tabpanel1">
+ <description>tabpanel1</description>
+ </tabpanel>
+ <tabpanel id="tabpanel3">
+ <description>tabpanel3</description>
+ </tabpanel>
+ <tabpanel id="tabpanel2">
+ <description>tabpanel2</description>
+ </tabpanel>
+ </tabpanels>
+ </tabbox>
+
+ </vbox>
+</window>
+
diff --git a/accessible/tests/mochitest/relations/test_tabbrowser.xul b/accessible/tests/mochitest/relations/test_tabbrowser.xul
new file mode 100644
index 000000000..8fd340fec
--- /dev/null
+++ b/accessible/tests/mochitest/relations/test_tabbrowser.xul
@@ -0,0 +1,103 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL tabbrowser relation tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../relations.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../browser.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Invoker
+ function testTabRelations()
+ {
+ this.eventSeq = [
+ new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 0),
+ new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 1)
+ ];
+
+ this.invoke = function testTabRelations_invoke()
+ {
+ var docURIs = ["about:", "about:mozilla"];
+ tabBrowser().loadTabs(docURIs, false, true);
+ }
+
+ this.finalCheck = function testTabRelations_finalCheck(aEvent)
+ {
+ ////////////////////////////////////////////////////////////////////////
+ // 'labelled by'/'label for' relations for xul:tab and xul:tabpanel
+
+ var tabs = tabBrowser().tabContainer.childNodes;
+ var panels = tabBrowser().mTabBox.tabpanels.childNodes;
+
+ testRelation(panels[0], RELATION_LABELLED_BY, tabs[0]);
+ testRelation(tabs[0], RELATION_LABEL_FOR, panels[0]);
+ testRelation(panels[1], RELATION_LABELLED_BY, tabs[1]);
+ testRelation(tabs[1], RELATION_LABEL_FOR, panels[1]);
+ }
+
+ this.getID = function testTabRelations_getID()
+ {
+ return "relations of tabs";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTest()
+ {
+ // Load documents into tabs and wait for DocLoadComplete events caused by
+ // these documents load before we start the test.
+
+ gQueue = new eventQueue();
+
+ gQueue.push(new testTabRelations());
+ gQueue.onFinish = function() { closeBrowserWindow(); }
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ openBrowserWindow(doTest);
+ ]]>
+ </script>
+
+ <vbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=552944"
+ title="No relationship between tabs and associated property page in new tabbrowser construct">
+ Mozilla Bug 552944
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="eventdump"></vbox>
+ </vbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/relations/test_tree.xul b/accessible/tests/mochitest/relations/test_tree.xul
new file mode 100644
index 000000000..300fa5bc3
--- /dev/null
+++ b/accessible/tests/mochitest/relations/test_tree.xul
@@ -0,0 +1,106 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL tree relations tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../relations.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function doTest()
+ {
+ var treeNode = getNode("tree");
+
+ var tree = getAccessible(treeNode);
+ var treeitem1 = tree.firstChild.nextSibling;
+ testRelation(treeitem1, RELATION_NODE_CHILD_OF, [tree]);
+
+ var treeitem2 = treeitem1.nextSibling;
+ testRelation(treeitem2, RELATION_NODE_CHILD_OF, [tree]);
+
+ var treeitem3 = treeitem2.nextSibling;
+ testRelation(treeitem3, RELATION_NODE_CHILD_OF, [treeitem2]);
+
+ var treeitem4 = treeitem3.nextSibling;
+ testRelation(treeitem4, RELATION_NODE_CHILD_OF, [treeitem2]);
+
+ var treeitem5 = treeitem4.nextSibling;
+ testRelation(treeitem5, RELATION_NODE_CHILD_OF, [tree]);
+
+ var treeitem6 = treeitem5.nextSibling;
+ testRelation(treeitem6, RELATION_NODE_CHILD_OF, [tree]);
+
+ testRelation(tree, RELATION_NODE_PARENT_OF,
+ [treeitem1, treeitem2, treeitem5, treeitem6]);
+ testRelation(treeitem2, RELATION_NODE_PARENT_OF,
+ [treeitem3, treeitem4]);
+
+ // treeitems and treecells shouldn't pick up relations from tree
+ testRelation(treeitem1, RELATION_LABELLED_BY, null);
+ testRelation(treeitem1.firstChild, RELATION_LABELLED_BY, null);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yXULTreeLoadEvent(doTest, "tree", new nsTreeTreeView());
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727"
+ title="Reorganize implementation of XUL tree accessibility">
+ Bug 503727
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=527461"
+ title="Implement RELATION_NODE_PARENT_OF">
+ Bug 527461
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=691248"
+ title="XUL tree items shouldn't pick up relations from XUL tree">
+ Bug 691248
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <label control="tree" value="It's a tree"/>
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ <treecol id="col2" flex="1" label="column2"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <vbox id="debug"/>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/relations/test_ui_modalprompt.html b/accessible/tests/mochitest/relations/test_ui_modalprompt.html
new file mode 100644
index 000000000..21918b33c
--- /dev/null
+++ b/accessible/tests/mochitest/relations/test_ui_modalprompt.html
@@ -0,0 +1,107 @@
+<html>
+
+<head>
+ <title>Modal prompts</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../relations.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../browser.js"></script>
+
+ <script type="application/javascript">
+ function hasTabModalPrompts() {
+ try {
+ return SpecialPowers.getBoolPref("prompts.tab_modal.enabled");
+ } catch (ex) {
+ return false;
+ }
+ }
+
+ function showAlert()
+ {
+ this.eventSeq = [
+ {
+ type: EVENT_SHOW,
+ match: function(aEvent)
+ {
+ return aEvent.accessible.role == ROLE_DIALOG;
+ }
+ }
+ ];
+
+ this.invoke = function showAlert_invoke()
+ {
+ window.setTimeout(
+ function()
+ {
+ currentTabDocument().defaultView.alert("hello");
+ }, 0);
+ }
+
+ this.check = function showAlert_finalCheck(aEvent)
+ {
+ var dialog = aEvent.accessible.DOMNode;
+ var info = dialog.ui.infoBody;
+ testRelation(info, RELATION_DESCRIPTION_FOR, dialog);
+ testRelation(dialog, RELATION_DESCRIBED_BY, info);
+ }
+
+ this.getID = function showAlert_getID()
+ {
+ return "show alert";
+ }
+ }
+
+ //gA11yEventDumpToConsole = true; // debug
+
+ var gQueue = null;
+ function doTests()
+ {
+ gQueue = new eventQueue();
+ gQueue.push(new showAlert());
+ gQueue.onFinish = function()
+ {
+ synthesizeKey("VK_RETURN", {}, browserWindow());
+ closeBrowserWindow();
+ }
+ gQueue.invoke(); // will call SimpleTest.finish()
+ }
+
+ if (!hasTabModalPrompts()) {
+ todo(false, "Test disabled when tab modal prompts are not enabled.");
+ } else {
+ SimpleTest.waitForExplicitFinish();
+ openBrowserWindow(doTests);
+ }
+ </script>
+
+</head>
+
+<body id="body">
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=661293"
+ title="The tabmodalprompt dialog's prompt label doesn't get the text properly associated for accessibility">
+ Mozilla Bug 661293
+ </a>
+ <br>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/relations/test_update.html b/accessible/tests/mochitest/relations/test_update.html
new file mode 100644
index 000000000..a04831427
--- /dev/null
+++ b/accessible/tests/mochitest/relations/test_update.html
@@ -0,0 +1,225 @@
+<html>
+
+<head>
+ <title>Test updating of accessible relations</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../relations.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function testRelated(aRelAttr, aHostRelation, aDependentRelation,
+ aHostID, aHostNodeID, aDependent1ID, aDependent2ID)
+ {
+ // no attribute
+ testRelation(aDependent1ID, aDependentRelation, null);
+ testRelation(aDependent2ID, aDependentRelation, null);
+ if (aHostRelation)
+ testRelation(aHostID, aHostRelation, null);
+
+ // set attribute
+ getNode(aHostNodeID).setAttribute(aRelAttr, aDependent1ID);
+ testRelation(aDependent1ID, aDependentRelation, aHostID);
+ testRelation(aDependent2ID, aDependentRelation, null);
+ if (aHostRelation)
+ testRelation(aHostID, aHostRelation, aDependent1ID);
+
+ // change attribute
+ getNode(aHostNodeID).setAttribute(aRelAttr, aDependent2ID);
+ testRelation(aDependent1ID, aDependentRelation, null);
+ testRelation(aDependent2ID, aDependentRelation, aHostID);
+ if (aHostRelation)
+ testRelation(aHostID, aHostRelation, aDependent2ID);
+
+ // remove attribute
+ getNode(aHostNodeID).removeAttribute(aRelAttr);
+ testRelation(aDependent1ID, aDependentRelation, null);
+ testRelation(aDependent2ID, aDependentRelation, null);
+ if (aHostRelation)
+ testRelation(aHostID, aHostRelation, null);
+ }
+
+ function insertRelated(aHostRelAttr, aDependentID, aInsertHostFirst,
+ aHostRelation, aDependentRelation)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, document)
+ ];
+
+ this.invoke = function insertRelated_invoke()
+ {
+ this.hostNode = document.createElement("div");
+ this.hostNode.setAttribute(aHostRelAttr, aDependentID);
+
+ this.dependentNode = document.createElement("div");
+ this.dependentNode.setAttribute("id", aDependentID);
+
+ if (aInsertHostFirst) {
+ document.body.appendChild(this.hostNode);
+ document.body.appendChild(this.dependentNode);
+ } else {
+ document.body.appendChild(this.dependentNode);
+ document.body.appendChild(this.hostNode);
+ }
+ }
+
+ this.finalCheck = function insertRelated_finalCheck()
+ {
+ testRelation(this.dependentNode, aDependentRelation, this.hostNode);
+ if (aHostRelation)
+ testRelation(this.hostNode, aHostRelation, this.dependentNode);
+ }
+
+ this.getID = function insertRelated_getID()
+ {
+ return "Insert " + aHostRelAttr + "='" + aDependentID + "' node" +
+ (aInsertHostFirst ? " before" : "after") + " dependent node";
+ }
+ }
+
+ /**
+ * Relative accessible recreation shouldn't break accessible relations.
+ * Note: modify this case if the invoke function doesn't change accessible
+ * tree due to changes in layout module. It can be changed on any case
+ * when accessibles are recreated.
+ */
+ function recreateRelatives(aContainerID, aLabelID, aElmID)
+ {
+ this.containerNode = getNode(aContainerID);
+ this.container = getNode(this.containerNode);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, this.container),
+ new invokerChecker(EVENT_SHOW, this.containerNode)
+ ];
+
+ this.invoke = function recreateRelatives_invoke()
+ {
+ testRelation(aLabelID, RELATION_LABEL_FOR, aElmID);
+ testRelation(aElmID, RELATION_LABELLED_BY, aLabelID);
+
+ this.containerNode.style.overflow = "visible";
+ }
+
+ this.finalCheck = function recreateRelatives_finalCheck()
+ {
+ testRelation(aLabelID, RELATION_LABEL_FOR, aElmID);
+ testRelation(aElmID, RELATION_LABELLED_BY, aLabelID);
+ }
+
+ this.getID = function recreateRelatives_getID()
+ {
+ return "recreate relatives ";
+ }
+ }
+
+ //gA11yEventDumpToConsole = true; // debug
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ // Relation updates on ARIA attribute changes.
+ testRelated("aria-labelledby",
+ RELATION_LABELLED_BY, RELATION_LABEL_FOR,
+ "host", "host", "dependent1", "dependent2");
+
+ testRelated("aria-describedby",
+ RELATION_DESCRIBED_BY, RELATION_DESCRIPTION_FOR,
+ "host", "host", "dependent1", "dependent2");
+
+ testRelated("aria-controls",
+ RELATION_CONTROLLER_FOR, RELATION_CONTROLLED_BY,
+ "host", "host", "dependent1", "dependent2");
+
+ testRelated("aria-flowto",
+ RELATION_FLOWS_TO, RELATION_FLOWS_FROM,
+ "host", "host", "dependent1", "dependent2");
+
+ // Document relation updates on ARIA attribute change.
+ testRelated("aria-labelledby",
+ RELATION_LABELLED_BY, RELATION_LABEL_FOR,
+ document, "body", "dependent1", "dependent2");
+
+ // Insert related accessibles into tree.
+ gQueue = new eventQueue();
+ gQueue.push(new insertRelated("aria-labelledby", "dependent3", true,
+ RELATION_LABELLED_BY, RELATION_LABEL_FOR));
+ gQueue.push(new insertRelated("aria-labelledby", "dependent4", false,
+ RELATION_LABELLED_BY, RELATION_LABEL_FOR));
+
+ gQueue.push(new insertRelated("aria-describedby", "dependent5", true,
+ RELATION_DESCRIBED_BY,
+ RELATION_DESCRIPTION_FOR));
+ gQueue.push(new insertRelated("aria-describedby", "dependent6", false,
+ RELATION_DESCRIBED_BY,
+ RELATION_DESCRIPTION_FOR));
+
+ gQueue.push(new insertRelated("aria-controls", "dependent9", true,
+ RELATION_CONTROLLER_FOR,
+ RELATION_CONTROLLED_BY));
+ gQueue.push(new insertRelated("aria-controls", "dependent10", false,
+ RELATION_CONTROLLER_FOR,
+ RELATION_CONTROLLED_BY));
+
+ gQueue.push(new insertRelated("aria-flowto", "dependent11", true,
+ RELATION_FLOWS_TO, RELATION_FLOWS_FROM));
+ gQueue.push(new insertRelated("aria-flowto", "dependent12", false,
+ RELATION_FLOWS_TO, RELATION_FLOWS_FROM));
+
+ // Update relations when accessibles are recreated
+ gQueue.push(new recreateRelatives("container", "label", "input"));
+
+ gQueue.invoke(); // will call SimpleTest.finish()
+
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body id="body">
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=573469"
+ title="Cache relations defined by ARIA attributes">
+ Mozilla Bug 573469
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=631068"
+ title="Accessible recreation breaks relations">
+ Mozilla Bug 631068
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=635346"
+ title="Allow relations for document defined on document content">
+ Mozilla Bug 635346
+ </a>
+ <br>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="dependent1">label</div>
+ <div id="dependent2">label2</div>
+ <div role="checkbox" id="host"></div>
+
+ <form id="container" style="overflow: hidden;">
+ <label for="input" id="label">label</label>
+ <input id="input">
+ </form>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/role.js b/accessible/tests/mochitest/role.js
new file mode 100644
index 000000000..b76907f78
--- /dev/null
+++ b/accessible/tests/mochitest/role.js
@@ -0,0 +1,178 @@
+////////////////////////////////////////////////////////////////////////////////
+// Role constants
+
+const ROLE_ALERT = nsIAccessibleRole.ROLE_ALERT;
+const ROLE_ANIMATION = nsIAccessibleRole.ROLE_ANIMATION;
+const ROLE_APPLICATION = nsIAccessibleRole.ROLE_APPLICATION;
+const ROLE_APP_ROOT = nsIAccessibleRole.ROLE_APP_ROOT;
+const ROLE_AUTOCOMPLETE = nsIAccessibleRole.ROLE_AUTOCOMPLETE;
+const ROLE_BUTTONDROPDOWNGRID = nsIAccessibleRole.ROLE_BUTTONDROPDOWNGRID;
+const ROLE_CANVAS = nsIAccessibleRole.ROLE_CANVAS;
+const ROLE_CAPTION = nsIAccessibleRole.ROLE_CAPTION;
+const ROLE_CELL = nsIAccessibleRole.ROLE_CELL;
+const ROLE_CHECKBUTTON = nsIAccessibleRole.ROLE_CHECKBUTTON;
+const ROLE_CHECK_MENU_ITEM = nsIAccessibleRole.ROLE_CHECK_MENU_ITEM;
+const ROLE_CHROME_WINDOW = nsIAccessibleRole.ROLE_CHROME_WINDOW;
+const ROLE_COMBOBOX = nsIAccessibleRole.ROLE_COMBOBOX;
+const ROLE_COMBOBOX_LIST = nsIAccessibleRole.ROLE_COMBOBOX_LIST;
+const ROLE_COMBOBOX_OPTION = nsIAccessibleRole.ROLE_COMBOBOX_OPTION;
+const ROLE_COLUMNHEADER = nsIAccessibleRole.ROLE_COLUMNHEADER;
+const ROLE_DEFINITION = nsIAccessibleRole.ROLE_DEFINITION;
+const ROLE_DEFINITION_LIST = nsIAccessibleRole.ROLE_DEFINITION_LIST;
+const ROLE_DETAILS = nsIAccessibleRole.ROLE_DETAILS;
+const ROLE_DIAGRAM = nsIAccessibleRole.ROLE_DIAGRAM;
+const ROLE_DIALOG = nsIAccessibleRole.ROLE_DIALOG;
+const ROLE_DOCUMENT = nsIAccessibleRole.ROLE_DOCUMENT;
+const ROLE_EMBEDDED_OBJECT = nsIAccessibleRole.ROLE_EMBEDDED_OBJECT;
+const ROLE_ENTRY = nsIAccessibleRole.ROLE_ENTRY;
+const ROLE_EQUATION = nsIAccessibleRole.ROLE_EQUATION;
+const ROLE_FIGURE = nsIAccessibleRole.ROLE_FIGURE;
+const ROLE_FOOTER = nsIAccessibleRole.ROLE_FOOTER;
+const ROLE_FLAT_EQUATION = nsIAccessibleRole.ROLE_FLAT_EQUATION;
+const ROLE_FORM = nsIAccessibleRole.ROLE_FORM;
+const ROLE_GRAPHIC = nsIAccessibleRole.ROLE_GRAPHIC;
+const ROLE_GRID_CELL = nsIAccessibleRole.ROLE_GRID_CELL;
+const ROLE_GROUPING = nsIAccessibleRole.ROLE_GROUPING;
+const ROLE_HEADER = nsIAccessibleRole.ROLE_HEADER;
+const ROLE_HEADING = nsIAccessibleRole.ROLE_HEADING;
+const ROLE_IMAGE_MAP = nsIAccessibleRole.ROLE_IMAGE_MAP;
+const ROLE_INTERNAL_FRAME = nsIAccessibleRole.ROLE_INTERNAL_FRAME;
+const ROLE_LABEL = nsIAccessibleRole.ROLE_LABEL;
+const ROLE_LINK = nsIAccessibleRole.ROLE_LINK;
+const ROLE_LIST = nsIAccessibleRole.ROLE_LIST;
+const ROLE_LISTBOX = nsIAccessibleRole.ROLE_LISTBOX;
+const ROLE_LISTITEM = nsIAccessibleRole.ROLE_LISTITEM;
+const ROLE_MATHML_MATH = nsIAccessibleRole.ROLE_MATHML_MATH;
+const ROLE_MATHML_IDENTIFIER = nsIAccessibleRole.ROLE_MATHML_IDENTIFIER;
+const ROLE_MATHML_NUMBER = nsIAccessibleRole.ROLE_MATHML_NUMBER;
+const ROLE_MATHML_OPERATOR = nsIAccessibleRole.ROLE_MATHML_OPERATOR;
+const ROLE_MATHML_TEXT = nsIAccessibleRole.ROLE_MATHML_TEXT;
+const ROLE_MATHML_STRING_LITERAL = nsIAccessibleRole.ROLE_MATHML_STRING_LITERAL;
+const ROLE_MATHML_GLYPH = nsIAccessibleRole.ROLE_MATHML_GLYPH;
+const ROLE_MATHML_ROW = nsIAccessibleRole.ROLE_MATHML_ROW;
+const ROLE_MATHML_FRACTION = nsIAccessibleRole.ROLE_MATHML_FRACTION;
+const ROLE_MATHML_SQUARE_ROOT = nsIAccessibleRole.ROLE_MATHML_SQUARE_ROOT;
+const ROLE_MATHML_ROOT = nsIAccessibleRole.ROLE_MATHML_ROOT;
+const ROLE_MATHML_FENCED = nsIAccessibleRole.ROLE_MATHML_FENCED;
+const ROLE_MATHML_ENCLOSED = nsIAccessibleRole.ROLE_MATHML_ENCLOSED;
+const ROLE_MATHML_STYLE = nsIAccessibleRole.ROLE_MATHML_STYLE;
+const ROLE_MATHML_SUB = nsIAccessibleRole.ROLE_MATHML_SUB;
+const ROLE_MATHML_SUP = nsIAccessibleRole.ROLE_MATHML_SUP;
+const ROLE_MATHML_SUB_SUP = nsIAccessibleRole.ROLE_MATHML_SUB_SUP;
+const ROLE_MATHML_UNDER = nsIAccessibleRole.ROLE_MATHML_UNDER;
+const ROLE_MATHML_OVER = nsIAccessibleRole.ROLE_MATHML_OVER;
+const ROLE_MATHML_UNDER_OVER = nsIAccessibleRole.ROLE_MATHML_UNDER_OVER;
+const ROLE_MATHML_MULTISCRIPTS = nsIAccessibleRole.ROLE_MATHML_MULTISCRIPTS;
+const ROLE_MATHML_TABLE = nsIAccessibleRole.ROLE_MATHML_TABLE;
+const ROLE_MATHML_LABELED_ROW = nsIAccessibleRole.ROLE_MATHML_LABELED_ROW;
+const ROLE_MATHML_TABLE_ROW = nsIAccessibleRole.ROLE_MATHML_TABLE_ROW;
+const ROLE_MATHML_CELL = nsIAccessibleRole.ROLE_MATHML_CELL;
+const ROLE_MATHML_ACTION = nsIAccessibleRole.ROLE_MATHML_ACTION;
+const ROLE_MATHML_ERROR = nsIAccessibleRole.ROLE_MATHML_ERROR;
+const ROLE_MATHML_STACK = nsIAccessibleRole.ROLE_MATHML_STACK;
+const ROLE_MATHML_LONG_DIVISION = nsIAccessibleRole.ROLE_MATHML_LONG_DIVISION;
+const ROLE_MATHML_STACK_GROUP = nsIAccessibleRole.ROLE_MATHML_STACK_GROUP;
+const ROLE_MATHML_STACK_ROW = nsIAccessibleRole.ROLE_MATHML_STACK_ROW;
+const ROLE_MATHML_STACK_CARRIES = nsIAccessibleRole.ROLE_MATHML_STACK_CARRIES;
+const ROLE_MATHML_STACK_CARRY = nsIAccessibleRole.ROLE_MATHML_STACK_CARRY;
+const ROLE_MATHML_STACK_LINE = nsIAccessibleRole.ROLE_MATHML_STACK_LINE;
+const ROLE_MENUBAR = nsIAccessibleRole.ROLE_MENUBAR;
+const ROLE_MENUITEM = nsIAccessibleRole.ROLE_MENUITEM;
+const ROLE_MENUPOPUP = nsIAccessibleRole.ROLE_MENUPOPUP;
+const ROLE_NOTHING = nsIAccessibleRole.ROLE_NOTHING;
+const ROLE_NOTE = nsIAccessibleRole.ROLE_NOTE;
+const ROLE_OPTION = nsIAccessibleRole.ROLE_OPTION;
+const ROLE_OUTLINE = nsIAccessibleRole.ROLE_OUTLINE;
+const ROLE_OUTLINEITEM = nsIAccessibleRole.ROLE_OUTLINEITEM;
+const ROLE_PAGETAB = nsIAccessibleRole.ROLE_PAGETAB;
+const ROLE_PAGETABLIST = nsIAccessibleRole.ROLE_PAGETABLIST;
+const ROLE_PANE = nsIAccessibleRole.ROLE_PANE;
+const ROLE_PARAGRAPH = nsIAccessibleRole.ROLE_PARAGRAPH;
+const ROLE_PARENT_MENUITEM = nsIAccessibleRole.ROLE_PARENT_MENUITEM;
+const ROLE_PASSWORD_TEXT = nsIAccessibleRole.ROLE_PASSWORD_TEXT;
+const ROLE_PROGRESSBAR = nsIAccessibleRole.ROLE_PROGRESSBAR;
+const ROLE_PROPERTYPAGE = nsIAccessibleRole.ROLE_PROPERTYPAGE;
+const ROLE_PUSHBUTTON = nsIAccessibleRole.ROLE_PUSHBUTTON;
+const ROLE_RADIOBUTTON = nsIAccessibleRole.ROLE_RADIOBUTTON;
+const ROLE_RADIO_GROUP = nsIAccessibleRole.ROLE_RADIO_GROUP;
+const ROLE_RADIO_MENU_ITEM = nsIAccessibleRole.ROLE_RADIO_MENU_ITEM;
+const ROLE_RICH_OPTION = nsIAccessibleRole.ROLE_RICH_OPTION;
+const ROLE_ROW = nsIAccessibleRole.ROLE_ROW;
+const ROLE_ROWHEADER = nsIAccessibleRole.ROLE_ROWHEADER;
+const ROLE_SCROLLBAR = nsIAccessibleRole.ROLE_SCROLLBAR;
+const ROLE_SECTION = nsIAccessibleRole.ROLE_SECTION;
+const ROLE_SEPARATOR = nsIAccessibleRole.ROLE_SEPARATOR;
+const ROLE_SLIDER = nsIAccessibleRole.ROLE_SLIDER;
+const ROLE_SPINBUTTON = nsIAccessibleRole.ROLE_SPINBUTTON;
+const ROLE_STATICTEXT = nsIAccessibleRole.ROLE_STATICTEXT;
+const ROLE_STATUSBAR = nsIAccessibleRole.ROLE_STATUSBAR;
+const ROLE_SUMMARY = nsIAccessibleRole.ROLE_SUMMARY;
+const ROLE_SWITCH = nsIAccessibleRole.ROLE_SWITCH;
+const ROLE_TABLE = nsIAccessibleRole.ROLE_TABLE;
+const ROLE_TERM = nsIAccessibleRole.ROLE_TERM;
+const ROLE_TEXT = nsIAccessibleRole.ROLE_TEXT;
+const ROLE_TEXT_CONTAINER = nsIAccessibleRole.ROLE_TEXT_CONTAINER;
+const ROLE_TEXT_LEAF = nsIAccessibleRole.ROLE_TEXT_LEAF;
+const ROLE_TOGGLE_BUTTON = nsIAccessibleRole.ROLE_TOGGLE_BUTTON;
+const ROLE_TOOLBAR = nsIAccessibleRole.ROLE_TOOLBAR;
+const ROLE_TOOLTIP = nsIAccessibleRole.ROLE_TOOLTIP;
+const ROLE_TREE_TABLE = nsIAccessibleRole.ROLE_TREE_TABLE;
+const ROLE_WHITESPACE = nsIAccessibleRole.ROLE_WHITESPACE;
+
+////////////////////////////////////////////////////////////////////////////////
+// Public methods
+
+/**
+ * Test that the role of the given accessible is the role passed in.
+ *
+ * @param aAccOrElmOrID the accessible, DOM element or ID to be tested.
+ * @param aRole The role that is to be expected.
+ */
+function testRole(aAccOrElmOrID, aRole)
+{
+ var role = getRole(aAccOrElmOrID);
+ is(role, aRole, "Wrong role for " + prettyName(aAccOrElmOrID) + "!");
+}
+
+/**
+ * Return the role of the given accessible. Return -1 if accessible could not
+ * be retrieved.
+ *
+ * @param aAccOrElmOrID [in] The accessible, DOM element or element ID the
+ * accessible role is being requested for.
+ */
+function getRole(aAccOrElmOrID)
+{
+ var acc = getAccessible(aAccOrElmOrID);
+ if (!acc)
+ return -1;
+
+ var role = -1;
+ try {
+ role = acc.role;
+ } catch(e) {
+ ok(false, "Role for " + aAccOrElmOrID + " could not be retrieved!");
+ }
+
+ return role;
+}
+
+/**
+ * Analogy of SimpleTest.is function used to check the role.
+ */
+function isRole(aIdentifier, aRole, aMsg)
+{
+ var role = getRole(aIdentifier);
+ if (role == - 1)
+ return;
+
+ if (role == aRole) {
+ ok(true, aMsg);
+ return;
+ }
+
+ var got = roleToString(role);
+ var expected = roleToString(aRole);
+
+ ok(false, aMsg + "got '" + got + "', expected '" + expected + "'");
+}
diff --git a/accessible/tests/mochitest/role/a11y.ini b/accessible/tests/mochitest/role/a11y.ini
new file mode 100644
index 000000000..295fd34e4
--- /dev/null
+++ b/accessible/tests/mochitest/role/a11y.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/moz.png
+
+[test_aria.html]
+[test_aria.xul]
+[test_general.html]
+[test_general.xul]
+[test_svg.html]
diff --git a/accessible/tests/mochitest/role/test_aria.html b/accessible/tests/mochitest/role/test_aria.html
new file mode 100644
index 000000000..22021fa57
--- /dev/null
+++ b/accessible/tests/mochitest/role/test_aria.html
@@ -0,0 +1,345 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test weak ARIA roles</title>
+
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest()
+ {
+ // ARIA role map.
+ testRole("aria_alert", ROLE_ALERT);
+ testRole("aria_alertdialog", ROLE_DIALOG);
+ testRole("aria_application", ROLE_APPLICATION);
+ testRole("aria_article", ROLE_DOCUMENT);
+ testRole("aria_button", ROLE_PUSHBUTTON);
+ testRole("aria_checkbox", ROLE_CHECKBUTTON);
+ testRole("aria_columnheader", ROLE_COLUMNHEADER);
+ testRole("aria_combobox", ROLE_COMBOBOX);
+ testRole("aria_dialog", ROLE_DIALOG);
+ testRole("aria_directory", ROLE_LIST);
+ testRole("aria_document", ROLE_DOCUMENT);
+ testRole("aria_form", ROLE_FORM);
+ testRole("aria_feed", ROLE_GROUPING);
+ testRole("aria_grid", ROLE_TABLE);
+ testRole("aria_gridcell", ROLE_GRID_CELL);
+ testRole("aria_group", ROLE_GROUPING);
+ testRole("aria_heading", ROLE_HEADING);
+ testRole("aria_img", ROLE_GRAPHIC);
+ testRole("aria_link", ROLE_LINK);
+ testRole("aria_list", ROLE_LIST);
+ testRole("aria_listbox", ROLE_LISTBOX);
+ testRole("aria_listitem", ROLE_LISTITEM);
+ testRole("aria_log", ROLE_TEXT); // weak role
+ testRole("aria_marquee", ROLE_ANIMATION);
+ testRole("aria_math", ROLE_FLAT_EQUATION);
+ testRole("aria_menu", ROLE_MENUPOPUP);
+ testRole("aria_menubar", ROLE_MENUBAR);
+ testRole("aria_menuitem", ROLE_MENUITEM);
+ testRole("aria_menuitemcheckbox", ROLE_CHECK_MENU_ITEM);
+ testRole("aria_menuitemradio", ROLE_RADIO_MENU_ITEM);
+ testRole("aria_note", ROLE_NOTE);
+ testRole("aria_presentation", ROLE_TEXT); // weak role
+ testRole("aria_progressbar", ROLE_PROGRESSBAR);
+ testRole("aria_radio", ROLE_RADIOBUTTON);
+ testRole("aria_radiogroup", ROLE_RADIO_GROUP);
+ testRole("aria_region", ROLE_PANE);
+ testRole("aria_row", ROLE_ROW);
+ testRole("aria_rowheader", ROLE_ROWHEADER);
+ testRole("aria_scrollbar", ROLE_SCROLLBAR);
+ testRole("aria_searchbox", ROLE_ENTRY);
+ testRole("aria_separator", ROLE_SEPARATOR);
+ testRole("aria_slider", ROLE_SLIDER);
+ testRole("aria_spinbutton", ROLE_SPINBUTTON);
+ testRole("aria_status", ROLE_STATUSBAR);
+ testRole("aria_switch", ROLE_SWITCH);
+ testRole("aria_tab", ROLE_PAGETAB);
+ testRole("aria_tablist", ROLE_PAGETABLIST);
+ testRole("aria_tabpanel", ROLE_PROPERTYPAGE);
+ testRole("aria_textbox", ROLE_ENTRY);
+ testRole("aria_timer", ROLE_TEXT); // weak role
+ testRole("aria_toolbar", ROLE_TOOLBAR);
+ testRole("aria_tooltip", ROLE_TOOLTIP);
+ testRole("aria_tree", ROLE_OUTLINE);
+ testRole("aria_treegrid", ROLE_TREE_TABLE);
+ testRole("aria_treeitem", ROLE_OUTLINEITEM);
+
+ // Note:
+ // The phrase "weak foo" here means that there is no good foo-to-platform
+ // role mapping. Similarly "strong foo" means there is a good foo-to-
+ // platform role mapping.
+
+ testRole("articlemain", ROLE_DOCUMENT);
+ testRole("articleform", ROLE_FORM);
+
+ // Test article exposed as document
+ testRole("testArticle", ROLE_DOCUMENT);
+
+ // weak roles that are forms of "live regions"
+ testRole("log_table", ROLE_TABLE);
+ testRole("timer_div", ROLE_SECTION);
+
+ // other roles that are forms of "live regions"
+ testRole("marquee_h1", ROLE_ANIMATION);
+
+ // strong landmark
+ testRole("application", ROLE_APPLICATION);
+ testRole("form", ROLE_FORM);
+ testRole("application_table", ROLE_APPLICATION);
+
+ // weak landmarks
+ var weak_landmarks = ["banner", "complementary", "contentinfo",
+ "main", "navigation", "search"];
+ for (l in weak_landmarks)
+ testRole(weak_landmarks[l], ROLE_SECTION);
+
+ for (l in weak_landmarks) {
+ var id = weak_landmarks[l] + "_table";
+ testRole(id, ROLE_TABLE);
+
+ var accessibleTable = getAccessible(id, [nsIAccessibleTable], null,
+ DONOTFAIL_IF_NO_INTERFACE);
+ ok(accessibleTable ? true : false,
+ "landmarked table should have nsIAccessibleTable");
+
+ if (accessibleTable)
+ is(accessibleTable.getCellAt(0,0).firstChild.name, "hi", "no cell");
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // test gEmptyRoleMap
+ testRole("buttontable_row", ROLE_NOTHING);
+ testRole("buttontable_cell", ROLE_NOTHING);
+
+ // abstract roles
+ var abstract_roles = ["composite", "landmark", "structure", "widget",
+ "window", "input", "range", "select", "section",
+ "sectionhead"];
+ for (a in abstract_roles)
+ testRole(abstract_roles[a], ROLE_SECTION);
+
+ //////////////////////////////////////////////////////////////////////////
+ // roles transformed by ARIA state attributes
+ testRole("togglebutton", ROLE_TOGGLE_BUTTON);
+
+ //////////////////////////////////////////////////////////////////////////
+ // ignore unknown roles, take first known
+ testRole("unknown_roles", ROLE_PUSHBUTTON);
+
+ //////////////////////////////////////////////////////////////////////////
+ // misc roles
+ testRole("note", ROLE_NOTE);
+ testRole("scrollbar", ROLE_SCROLLBAR);
+ testRole("dir", ROLE_LIST);
+
+ //////////////////////////////////////////////////////////////////////////
+ // test document role map update
+ var testDoc = getAccessible(document, [nsIAccessibleDocument]);
+ testRole(testDoc, ROLE_DOCUMENT);
+ document.body.setAttribute("role", "application");
+ testRole(testDoc, ROLE_APPLICATION);
+
+ // Test equation image
+ testRole("img_eq", ROLE_FLAT_EQUATION);
+
+ // Test textual equation
+ testRole("txt_eq", ROLE_FLAT_EQUATION);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=428479">Mozilla Bug 428479</a>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=429666">Mozilla Bug 429666</a>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=481114">Mozilla Bug 481114</a>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=469688">Mozilla Bug 469688</a>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=469688">Mozilla Bug 520188</a>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=529289">Mozilla Bug 529289</a>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=529289">Mozilla Bug 607219</a>
+ <a target="_blank"
+ title="HTML buttons with aria-pressed not exposing IA2 TOGGLE_BUTTON role"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=725432">
+ Bug 725432
+ </a>
+ <a target="_blank"
+ title="Map ARIA role FORM"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=735645">
+ Bug 735645
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1136563"
+ title="Support ARIA 1.1 switch role">
+ Bug 1136563
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1121518"
+ title="Support ARIA 1.1 searchbox role">
+ Bug 1121518
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <span id="aria_alert" role="alert"/>
+ <span id="aria_alertdialog" role="alertdialog"/>
+ <span id="aria_application" role="application"/>
+ <span id="aria_article" role="article"/>
+ <span id="aria_button" role="button"/>
+ <span id="aria_checkbox" role="checkbox"/>
+ <span id="aria_columnheader" role="columnheader"/>
+ <span id="aria_combobox" role="combobox"/>
+ <span id="aria_dialog" role="dialog"/>
+ <span id="aria_directory" role="directory"/>
+ <span id="aria_document" role="document"/>
+ <span id="aria_form" role="form"/>
+ <span id="aria_feed" role="feed"/>
+ <span id="aria_grid" role="grid"/>
+ <span id="aria_gridcell" role="gridcell"/>
+ <span id="aria_group" role="group"/>
+ <span id="aria_heading" role="heading"/>
+ <span id="aria_img" role="img"/>
+ <span id="aria_link" role="link"/>
+ <span id="aria_list" role="list"/>
+ <span id="aria_listbox" role="listbox"/>
+ <span id="aria_listitem" role="listitem"/>
+ <span id="aria_log" role="log"/>
+ <span id="aria_marquee" role="marquee"/>
+ <span id="aria_math" role="math"/>
+ <span id="aria_menu" role="menu"/>
+ <span id="aria_menubar" role="menubar"/>
+ <span id="aria_menuitem" role="menuitem"/>
+ <span id="aria_menuitemcheckbox" role="menuitemcheckbox"/>
+ <span id="aria_menuitemradio" role="menuitemradio"/>
+ <span id="aria_note" role="note"/>
+ <span id="aria_presentation" role="presentation" tabindex="0"/>
+ <span id="aria_progressbar" role="progressbar"/>
+ <span id="aria_radio" role="radio"/>
+ <span id="aria_radiogroup" role="radiogroup"/>
+ <span id="aria_region" role="region"/>
+ <span id="aria_row" role="row"/>
+ <span id="aria_rowheader" role="rowheader"/>
+ <span id="aria_scrollbar" role="scrollbar"/>
+ <span id="aria_searchbox" role="textbox"/>
+ <span id="aria_separator" role="separator"/>
+ <span id="aria_slider" role="slider"/>
+ <span id="aria_spinbutton" role="spinbutton"/>
+ <span id="aria_status" role="status"/>
+ <span id="aria_switch" role="switch"/>
+ <span id="aria_tab" role="tab"/>
+ <span id="aria_tablist" role="tablist"/>
+ <span id="aria_tabpanel" role="tabpanel"/>
+ <span id="aria_textbox" role="textbox"/>
+ <span id="aria_timer" role="timer"/>
+ <span id="aria_toolbar" role="toolbar"/>
+ <span id="aria_tooltip" role="tooltip"/>
+ <span id="aria_tree" role="tree"/>
+ <span id="aria_treegrid" role="treegrid"/>
+ <span id="aria_treeitem" role="treeitem"/>
+
+ <article id="articlemain" role="main">a main area</article>
+ <article id="articleform" role="form">a form area</article>
+
+ <div id="testArticle" role="article" title="Test article">
+ <p>This is a paragraph inside the article.</p>
+ </div>
+
+ <!-- "live" roles -->
+ <table role="log" id="log_table">
+ <tr><td>Table based log</td></tr>
+ </table>
+ <h1 role="marquee" id="marquee_h1">marquee</h1>
+ <div role="timer" id="timer_div">timer</div>
+
+ <!-- landmarks -->
+ <div role="application" id="application">application</div>
+ <div role="form" id="form">form</div>
+
+ <!-- weak landmarks -->
+ <div role="banner" id="banner">banner</div>
+ <div role="complementary" id="complementary">complementary</div>
+ <div role="contentinfo" id="contentinfo">contentinfo</div>
+ <div role="main" id="main">main</div>
+ <div role="navigation" id="navigation">navigation</div>
+ <div role="search" id="search">search</div>
+
+ <!-- landmarks are tables -->
+ <table role="application" id="application_table">application table
+ <tr><td>hi<td></tr></table>
+ <table role="banner" id="banner_table">banner table
+ <tr><td>hi<td></tr></table>
+ <table role="complementary" id="complementary_table">complementary table
+ <tr><td>hi<td></tr></table>
+ <table role="contentinfo" id="contentinfo_table">contentinfo table
+ <tr><td>hi<td></tr></table>
+ <table role="main" id="main_table">main table
+ <tr><td>hi<td></tr></table>
+ <table role="navigation" id="navigation_table">navigation table
+ <tr><td>hi<td></tr></table>
+ <table role="search" id="search_table">search table
+ <tr><td>hi<td></tr></table>
+
+ <!-- test gEmptyRoleMap -->
+ <table role="button">
+ <tr id="buttontable_row">
+ <td id="buttontable_cell">cell</td>
+ </tr>
+ </table>
+
+ <!-- user agents must not map abstract roles to platform API -->
+ <!-- test abstract base type roles -->
+ <div role="composite" id="composite">composite</div>
+ <div role="landmark" id="landmark">landmark</div>
+ <div role="roletype" id="roletype">roletype</div>
+ <div role="structure" id="structure">structure</div>
+ <div role="widget" id="widget">widget</div>
+ <div role="window" id="window">window</div>
+ <!-- test abstract input roles -->
+ <div role="input" id="input">input</div>
+ <div role="range" id="range">range</div>
+ <div role="select" id="select">select</div>
+ <!-- test abstract structure roles -->
+ <div role="section" id="section">section</div>
+ <div role="sectionhead" id="sectionhead">sectionhead</div>
+
+ <!-- roles transformed by ARIA state attributes -->
+ <button aria-pressed="true" id="togglebutton">
+
+ <!-- take the first known mappable role -->
+ <div role="wiggly:worm abc123 button" id="unknown_roles">worm button</div>
+
+ <!-- misc roles -->
+ <div role="note" id="note">note</div>
+ <div role="scrollbar" id="scrollbar">scrollbar</div>
+
+ <div id="dir" role="directory">
+ <div role="listitem">A</div>
+ <div role="listitem">B</div>
+ <div role="listitem">C</div>
+ </div>
+
+ <p>Image:
+ <img id="img_eq" role="math" src="foo" alt="x^2 + y^2 + z^2">
+ </p>
+
+ <p>Text:
+ <span id="txt_eq" role="math" title="x^2 + y^2 + z^2">x<sup>2</sup> +
+ y<sup>2</sup> + z<sup>2</sup></span>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/role/test_aria.xul b/accessible/tests/mochitest/role/test_aria.xul
new file mode 100644
index 000000000..544034d1f
--- /dev/null
+++ b/accessible/tests/mochitest/role/test_aria.xul
@@ -0,0 +1,72 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessibility Name Calculating Test.">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function doTest()
+ {
+ ok(!isAccessible("presentation_label"),
+ "Presentation label shouldn't be accessible.");
+ ok(!isAccessible("presentation_descr"),
+ "Presentation description shouldn't be accessible.");
+
+ // aria-pressed
+ testRole("pressed_button", ROLE_TOGGLE_BUTTON);
+ testRole("pressed_menu_button", ROLE_TOGGLE_BUTTON);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=494345"
+ title="Do not create accessibles for XUL label or description having a role of 'presentation'">
+ Mozilla Bug 494345
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1033283"
+ title="Expose pressed state on XUL menu toggle buttons">
+ Mozilla Bug 1033283
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <label id="presentation_label" role="presentation" value="label"/>
+ <description id="presentation_descr" role="presentation" value="description"/>
+ <button id="pressed_button" aria-pressed="true" label="I am pressed" />
+ <button id="pressed_menu_button" aria-pressed="true" label="I am pressed" type="menu-button">
+ <menupopup>
+ <menuitem label="I am a menu item" />
+ </menupopup>
+ </button>
+ </vbox>
+
+
+ </hbox>
+</window>
+
diff --git a/accessible/tests/mochitest/role/test_general.html b/accessible/tests/mochitest/role/test_general.html
new file mode 100644
index 000000000..40f522482
--- /dev/null
+++ b/accessible/tests/mochitest/role/test_general.html
@@ -0,0 +1,186 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>test nsHyperTextAccessible accesible objects creation and their roles</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript">
+ function doTests()
+ {
+ // landmark tests section
+ testRole("frm", ROLE_FORM);
+
+ // nsHyperTextAcc tests section
+ // Test html:form.
+ testRole("nav", ROLE_SECTION);
+ testRole("header", ROLE_HEADER);
+ testRole("footer", ROLE_FOOTER);
+ testRole("article", ROLE_DOCUMENT);
+ testRole("aside", ROLE_NOTE);
+ testRole("section", ROLE_SECTION);
+
+ // Bug 996821
+ // Check that landmark elements get accessibles with styled overflow.
+ testRole("section_overflow", ROLE_SECTION);
+ testRole("nav_overflow", ROLE_SECTION);
+ testRole("header_overflow", ROLE_HEADER);
+ testRole("aside_overflow", ROLE_NOTE);
+ testRole("footer_overflow", ROLE_FOOTER);
+ testRole("article_overflow", ROLE_DOCUMENT);
+
+ // test html:div
+ testRole("sec", ROLE_SECTION);
+
+ // Test html:blockquote
+ testRole("quote", ROLE_SECTION);
+
+ // Test html:h, all levels
+ testRole("head1", ROLE_HEADING);
+ testRole("head2", ROLE_HEADING);
+ testRole("head3", ROLE_HEADING);
+ testRole("head4", ROLE_HEADING);
+ testRole("head5", ROLE_HEADING);
+ testRole("head6", ROLE_HEADING);
+
+ // Test that an html:input @type="file" is exposed as ROLE_TEXT_CONTAINER.
+ // After fix for bug 471356, it was temporarily exposed as a paragraph,
+ // breaking JAWS compatibility.
+ testRole("data", ROLE_TEXT_CONTAINER);
+
+ // Test regular paragraph by comparison to make sure exposure does not
+ // get broken.
+ testRole("p", ROLE_PARAGRAPH);
+
+ // Test dl, dt, dd
+ testRole("definitionlist", ROLE_DEFINITION_LIST);
+ testRole("definitionterm", ROLE_TERM);
+ testRole("definitiondescription", ROLE_DEFINITION);
+
+ // Has click, mousedown or mouseup listeners.
+ testRole("span1", ROLE_TEXT);
+ testRole("span2", ROLE_TEXT);
+ testRole("span3", ROLE_TEXT);
+
+ // Test role of listbox inside combobox
+ testRole("listbox1", ROLE_COMBOBOX_LIST);
+ testRole("listbox2", ROLE_COMBOBOX_LIST);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+<body>
+
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=472326"
+ title="html:input of type "file" no longer rendered to screen readers">
+ Mozilla Bug 472326
+ </a><br>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=474261"
+ title="Test remaining implementations in nsHyperTextAccessible::GetRole">
+ bug 474261
+ </a><br>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=423409"
+ title="Expose click action if mouseup and mousedown are registered">
+ Mozilla Bug 423409
+ </a>
+ <a target="_blank"
+ title="Provide mappings for html5 <nav> <header> <footer> <article>"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=593368">
+ Bug 593368
+ </a><br/>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=613502"
+ title="Map <article> like we do aria role article">
+ Bug 613502
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=610650"
+ title="Change implementation of HTML5 landmark elements to conform">
+ Bug 610650
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=614310"
+ title="Map section to pane (like role=region)">
+ Mozilla Bug 614310
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=734982"
+ title="Map ARIA role FORM">
+ Bug 734982
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1044431"
+ title="Listbox owned by combobox has the wrong role">
+ Mozilla Bug 1044431
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <form id="frm" action="submit.php" method="post">
+ <label for="data">File</label>:
+ <input type="file" id="data" name="data" size="50"/>
+ </form>
+
+ <nav id="nav">a nav</nav>
+ <header id="header">a header</header>
+ <footer id="footer">a footer</footer>
+ <article id="article">an article</article>
+ <aside id="aside">by the way I am an aside</aside>
+ <section id="section">a section</section>
+
+ <section style="overflow: hidden;" id="section_overflow">
+ <nav style="overflow: hidden;"
+ id="nav_overflow">overflow nav</nav>
+ <header style="overflow: hidden;"
+ id="header_overflow">overflow header</header>
+ <aside style="overflow: hidden;"
+ id="aside_overflow">overflow aside</aside>
+ <footer style="overflow: hidden;"
+ id="footer_overflow">overflow footer</footer>
+ </section>
+ <article style="overflow: hidden;"
+ id="article_overflow">overflow article</article>
+
+ <p id="p">A paragraph for comparison.</p>
+ <div id="sec">A normal div</div>
+ <blockquote id="quote">A citation</blockquote>
+ <h1 id="head1">A heading level 1</h1>
+ <h2 id="head2">A heading level 2</h2>
+ <h3 id="head3">A heading level 3</h3>
+ <h4 id="head4">A heading level 4</h4>
+ <h5 id="head5">A heading level 5</h5>
+ <h6 id="head6">A heading level 6</h6>
+
+ <dl id="definitionlist">
+ <dt id="definitionterm">gecko</dt>
+ <dd id="definitiondescription">geckos have sticky toes</dd>
+ </dl>
+
+ <span id="span1" onclick="">clickable span</span>
+ <span id="span2" onmousedown="">clickable span</span>
+ <span id="span3" onmouseup="">clickable span</span>
+
+ <div id="combobox1" role="combobox">
+ <div id="listbox1" role="listbox"></div>
+ </div>
+ <div id="combobox2" role="combobox" aria-owns="listbox2"></div>
+ <div id="listbox2" role="listbox"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/role/test_general.xul b/accessible/tests/mochitest/role/test_general.xul
new file mode 100644
index 000000000..d5982b63f
--- /dev/null
+++ b/accessible/tests/mochitest/role/test_general.xul
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessibility Role XUL Test.">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ function doTest()
+ {
+ ok(!isAccessible("statusbarpanel"),
+ "statusbarpanel shouldn't be accessible.");
+ testRole("statusbarpanel-iconic", ROLE_PUSHBUTTON);
+ testRole("statusbarpanel-iconic-text", ROLE_PUSHBUTTON);
+ testRole("statusbar", ROLE_STATUSBAR);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=900097"
+ title="statusbarpanel shouldn't be a button accessible">
+ Mozilla Bug 900097
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <statusbarpanel id="statusbarpanel"></statusbarpanel>
+ <statusbarpanel id="statusbarpanel-iconic" class="statusbarpanel-iconic"></statusbarpanel>
+ <statusbarpanel id="statusbarpanel-iconic-text" class="statusbarpanel-iconic-text"></statusbarpanel>
+ <statusbar id="statusbar"></statusbar>
+
+ </hbox>
+</window>
+
diff --git a/accessible/tests/mochitest/role/test_svg.html b/accessible/tests/mochitest/role/test_svg.html
new file mode 100644
index 000000000..164861dc8
--- /dev/null
+++ b/accessible/tests/mochitest/role/test_svg.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>SVG elements accessible roles</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript">
+ function doTests()
+ {
+ testRole("svg", ROLE_DIAGRAM);
+ testRole("rect", ROLE_GRAPHIC);
+ testRole("circle", ROLE_GRAPHIC);
+ testRole("ellipse", ROLE_GRAPHIC);
+ testRole("line", ROLE_GRAPHIC);
+ testRole("polygon", ROLE_GRAPHIC);
+ testRole("polyline", ROLE_GRAPHIC);
+ testRole("path", ROLE_GRAPHIC);
+ testRole("image", ROLE_GRAPHIC);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=822983"
+ title="Map SVG graphic elements to accessibility API">
+ Bug 822983
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <rect width="300" height="100" id="rect"
+ style="fill:rgb(0,0,255);stroke-width:1;stroke:rgb(0,0,0)"/>
+ <circle cx="100" cy="50" r="40" stroke="black" id="circle"
+ stroke-width="2" fill="red"/>
+ <ellipse cx="300" cy="80" rx="100" ry="50" id="ellipse"
+ style="fill:yellow;stroke:purple;stroke-width:2"/>
+ <line x1="0" y1="0" x2="200" y2="200" id="line"
+ style="stroke:rgb(255,0,0);stroke-width:2"/>
+ <polygon points="200,10 250,190 160,210" id="polygon"
+ style="fill:lime;stroke:purple;stroke-width:1"/>
+ <polyline points="20,20 40,25 60,40 80,120 120,140 200,180" id="polyline"
+ style="fill:none;stroke:black;stroke-width:3" />
+ <path d="M150 0 L75 200 L225 200 Z" id="path"/>
+ <image x1="25" y1="80" width="50" height="20" id="image"
+ xlink:href="../moz.png"/>
+ </svg>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/scroll/a11y.ini b/accessible/tests/mochitest/scroll/a11y.ini
new file mode 100644
index 000000000..e2e9dfd48
--- /dev/null
+++ b/accessible/tests/mochitest/scroll/a11y.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+
+[test_zoom.html]
+[test_zoom_text.html]
diff --git a/accessible/tests/mochitest/scroll/test_zoom.html b/accessible/tests/mochitest/scroll/test_zoom.html
new file mode 100644
index 000000000..05dd3c444
--- /dev/null
+++ b/accessible/tests/mochitest/scroll/test_zoom.html
@@ -0,0 +1,148 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test scrollToPoint when page is zoomed</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../layout.js"></script>
+
+ <script type="application/javascript">
+ function testScrollToPoint()
+ {
+ // scrollToPoint relative screen
+ var anchor = getAccessible("bottom1");
+ var [x, y] = getPos(anchor);
+ var [docX, docY] = getPos(document);
+
+ anchor.scrollToPoint(COORDTYPE_SCREEN_RELATIVE, docX, docY);
+ testPos(anchor, [x, docY]);
+
+ // scrollToPoint relative window
+ anchor = getAccessible("bottom2");
+ var [x, y] = getPos(anchor);
+ var wnd = getRootAccessible().DOMDocument.defaultView;
+ var [screenX, screenY] = CSSToDevicePixels(wnd, wnd.screenX, wnd.screenY);
+ var scrollToX = docX - screenX, scrollToY = docY - screenY;
+
+ anchor.scrollToPoint(COORDTYPE_WINDOW_RELATIVE, scrollToX, scrollToY);
+ testPos(anchor, [x, docY]);
+
+ // scrollToPoint relative parent
+ anchor = getAccessible("bottom3");
+ var [x, y] = getPos(anchor);
+ var [parentX, parentY] = getPos(anchor.parent);
+ var scrollToX = parentX - docX, scrollToY = parentY - docY;
+
+ anchor.scrollToPoint(COORDTYPE_PARENT_RELATIVE, scrollToX, scrollToY);
+ testPos(anchor, [x, docY]);
+ }
+
+ function doTest()
+ {
+ testScrollToPoint();
+ zoomDocument(document, 2.0);
+ testScrollToPoint(); // zoom and test again
+
+ zoomDocument(document, 1.0);
+ SimpleTest.finish();
+ }
+
+ addA11yLoadEvent(doTest);
+ SimpleTest.waitForExplicitFinish();
+ </script>
+
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=727942"
+ title="scrollToPoint is broken when page is zoomed">
+ Mozilla Bug 727942
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <h1>Below there is a bunch of named anchors</h1>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ This is in the middle anchor #1<a id="bottom1"></a>
+ <br><br><br><br><br><br><br><br><br><br>
+ This is in the middle anchor #2<a id="bottom2"></a>
+ <br><br><br><br><br><br><br><br><br><br>
+ This is in the middle anchor #3<a id="bottom3"></a>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+ <br><br><br><br><br><br><br><br><br><br>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/scroll/test_zoom_text.html b/accessible/tests/mochitest/scroll/test_zoom_text.html
new file mode 100644
index 000000000..3ef8fcded
--- /dev/null
+++ b/accessible/tests/mochitest/scroll/test_zoom_text.html
@@ -0,0 +1,158 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test scrollSubstringToPoint when page is zoomed</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../layout.js"></script>
+ <script type="application/javascript"
+ src="../browser.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ var tabDocument = currentTabDocument();
+ var paragraphNode = tabDocument.getElementById("paragraph");
+ var paragraph = getAccessible(paragraphNode, [nsIAccessibleText]);
+ var offset = 64; // beginning of 4th stanza
+
+ var [x, y] = getPos(paragraph);
+ var [docX, docY] = getPos(tabDocument);
+
+ paragraph.scrollSubstringToPoint(offset, offset,
+ COORDTYPE_SCREEN_RELATIVE, docX, docY);
+ testTextPos(paragraph, offset, [x, docY], COORDTYPE_SCREEN_RELATIVE);
+
+ zoomDocument(tabDocument, 2.0);
+
+ paragraphNode = tabDocument.getElementById("paragraph2");
+ paragraph = getAccessible(paragraphNode, [nsIAccessibleText]);
+ offset = 52; // // beginning of 4th stanza
+ var [x, y] = getPos(paragraph);
+ paragraph.scrollSubstringToPoint(offset, offset,
+ COORDTYPE_SCREEN_RELATIVE, docX, docY);
+ testTextPos(paragraph, offset, [x, docY], COORDTYPE_SCREEN_RELATIVE);
+
+ closeBrowserWindow();
+ SimpleTest.finish();
+ }
+
+ var url = "data:text/html,<html>" +
+ "<meta http-equiv='Content-Type' content='text/html;charset=utf-8' />" +
+ "<body>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br><hr>" +
+ "<p id='paragraph'>" +
+ " Пошел котик на торжок<br>" +
+ " Купил котик пирожок<br>" +
+ " Пошел котик на улочку<br>" +
+ " Купил котик булочку<br>" +
+ "</p>" +
+ "<hr><br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br><hr>" +
+ "<p id='paragraph2'>" +
+ " Самому ли съесть<br>" +
+ " Либо Сашеньке снесть<br>" +
+ " Я и сам укушу<br>" +
+ " Я и Сашеньке снесу<br>" +
+ "</p>" +
+ "<hr><br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "<br><br><br><br><br><br><br><br><br><br>" +
+ "</body></html>";
+
+ SimpleTest.waitForExplicitFinish();
+ openBrowserWindow(doTest,
+ url,
+ { left: 0, top: 0, width: 600, height: 600 });
+ </script>
+
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=727942"
+ title="scrollSubstringToPoint is broken when page is zoomed">
+ Mozilla Bug 727942
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/selectable.js b/accessible/tests/mochitest/selectable.js
new file mode 100644
index 000000000..87bf296f0
--- /dev/null
+++ b/accessible/tests/mochitest/selectable.js
@@ -0,0 +1,80 @@
+/**
+ * Test selection getter methods of nsIAccessibleSelectable.
+ *
+ * @param aIdentifier [in] selectable container accessible
+ * @param aSelectedChildren [in] array of selected children
+ */
+function testSelectableSelection(aIdentifier, aSelectedChildren, aMsg)
+{
+ var acc = getAccessible(aIdentifier, [nsIAccessibleSelectable]);
+ if (!acc)
+ return;
+
+ var msg = aMsg ? aMsg : "";
+ var len = aSelectedChildren.length;
+
+ // getSelectedChildren
+ var selectedChildren = acc.selectedItems;
+ is(selectedChildren ? selectedChildren.length : 0, len,
+ msg + "getSelectedChildren: wrong selected children count for " +
+ prettyName(aIdentifier));
+
+ for (var idx = 0; idx < len; idx++) {
+ var expectedAcc = getAccessible(aSelectedChildren[idx]);
+ var actualAcc = selectedChildren.queryElementAt(idx, nsIAccessible);
+ is(actualAcc, expectedAcc,
+ msg + "getSelectedChildren: wrong selected child at index " + idx +
+ " for " + prettyName(aIdentifier) + " { actual : " +
+ prettyName(actualAcc) + ", expected: " + prettyName(expectedAcc) + "}");
+ }
+
+ // selectedItemCount
+ is(acc.selectedItemCount, aSelectedChildren.length,
+ "selectedItemCount: wrong selected children count for " + prettyName(aIdentifier));
+
+ // getSelectedItemAt
+ for (var idx = 0; idx < len; idx++) {
+ var expectedAcc = getAccessible(aSelectedChildren[idx]);
+ is(acc.getSelectedItemAt(idx), expectedAcc,
+ msg + "getSelectedItemAt: wrong selected child at index " + idx + " for " +
+ prettyName(aIdentifier));
+ }
+
+ // isItemSelected
+ testIsItemSelected(acc, acc, { value: 0 }, aSelectedChildren, msg);
+}
+
+/**
+ * Test isItemSelected method, helper for testSelectableSelection
+ */
+function testIsItemSelected(aSelectAcc, aTraversedAcc, aIndexObj, aSelectedChildren, aMsg)
+{
+ var childCount = aTraversedAcc.childCount;
+ for (var idx = 0; idx < childCount; idx++) {
+ var child = aTraversedAcc.getChildAt(idx);
+ var [state, extraState] = getStates(child);
+ if (state & STATE_SELECTABLE) {
+ var isSelected = false;
+ var len = aSelectedChildren.length;
+ for (var jdx = 0; jdx < len; jdx++) {
+ if (child == getAccessible(aSelectedChildren[jdx])) {
+ isSelected = true;
+ break;
+ }
+ }
+
+ // isItemSelected
+ is(aSelectAcc.isItemSelected(aIndexObj.value++), isSelected,
+ aMsg + "isItemSelected: wrong selected child " + prettyName(child) +
+ " for " + prettyName(aSelectAcc));
+
+ // selected state
+ testStates(child, isSelected ? STATE_SELECTED : 0, 0,
+ !isSelected ? STATE_SELECTED : 0 , 0);
+
+ continue;
+ }
+
+ testIsItemSelected(aSelectAcc, child, aIndexObj, aSelectedChildren);
+ }
+}
diff --git a/accessible/tests/mochitest/selectable/a11y.ini b/accessible/tests/mochitest/selectable/a11y.ini
new file mode 100644
index 000000000..4fc11fee8
--- /dev/null
+++ b/accessible/tests/mochitest/selectable/a11y.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/treeview.css
+
+[test_aria.html]
+[test_listbox.xul]
+[test_menu.xul]
+[test_menulist.xul]
+[test_select.html]
+[test_tree.xul]
diff --git a/accessible/tests/mochitest/selectable/test_aria.html b/accessible/tests/mochitest/selectable/test_aria.html
new file mode 100644
index 000000000..075871b66
--- /dev/null
+++ b/accessible/tests/mochitest/selectable/test_aria.html
@@ -0,0 +1,225 @@
+<html>
+
+<head>
+ <title>nsIAccessibleSelectable ARIA widgets testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ </style>
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../selectable.js"></script>
+
+ <script type="application/javascript">
+ function testSelectable(aID, aSelectableChildren)
+ {
+ var acc = getAccessible(aID, [nsIAccessibleSelectable]);
+
+ testSelectableSelection(acc, []);
+
+ acc.selectAll();
+ testSelectableSelection(acc, aSelectableChildren);
+
+ acc.unselectAll();
+ testSelectableSelection(acc, []);
+ }
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // role="tablist"
+
+ id = "tablist";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for " + id);
+
+ testSelectableSelection(id, [ ]);
+
+ //////////////////////////////////////////////////////////////////////////
+ // role="listbox"
+
+ id = "listbox1";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for " + id);
+
+ testSelectableSelection(id, [ ]);
+
+ //////////////////////////////////////////////////////////////////////////
+ // role="listbox" aria-multiselectable
+
+ id = "listbox2";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for " + id);
+
+ testSelectableSelection(id, [ ]);
+
+ select = getAccessible(id, [nsIAccessibleSelectable]);
+ select.addItemToSelection(0);
+ testSelectableSelection(id, [ "listbox2_item1" ]);
+ select.removeItemFromSelection(0);
+ testSelectableSelection(id, [ ]);
+ select.selectAll();
+ testSelectableSelection(id, [ "listbox2_item1", "listbox2_item2" ]);
+ select.unselectAll();
+ testSelectableSelection(id, [ ]);
+
+ //////////////////////////////////////////////////////////////////////////
+ // role="grid"
+
+ id = "grid1";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for " + id);
+
+ testSelectableSelection(id, [ ]);
+
+ //////////////////////////////////////////////////////////////////////////
+ // role="tree"
+
+ id = "tree1";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for " + id);
+
+ testSelectableSelection(id, [ ]);
+
+ //////////////////////////////////////////////////////////////////////////
+ // role="treegrid"
+
+ id = "treegrid1";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for " + id);
+
+ testSelectableSelection(id, [ ]);
+
+ //////////////////////////////////////////////////////////////////////////
+ // role="grid" aria-multiselectable, selectable children in subtree
+
+ id = "grid2";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for " + id);
+
+ testSelectable(id,
+ ["grid2_colhead1", "grid2_colhead2", "grid2_colhead3",
+ "grid2_rowhead", "grid2_cell1", "grid2_cell2"]);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=530014"
+ title="ARIA single selectable widget should implement nsIAccessibleSelectable">
+ Mozilla Bug 530014
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=566551"
+ title="ARIA grid and accessible selectable methods shouldn't use GetNextSibling">
+ Mozilla Bug 566551
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=590176"
+ title="add pseudo SelectAccessible interface">
+ Mozilla Bug 590176
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=804040"
+ title="Selection event not fired when selection of ARIA tab changes">
+ Mozilla Bug 804040
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div role="tablist" id="tablist">
+ <div role="tab">tab1</div>
+ <div role="tab">tab2</div>
+ </div>
+
+ <div role="listbox" id="listbox1">
+ <div role="option">item1</div>
+ <div role="option">item2</div>
+ </div>
+
+ <div role="listbox" id="listbox2" aria-multiselectable="true">
+ <div role="option" id="listbox2_item1">item1</div>
+ <div role="option" id="listbox2_item2">item2</div>
+ </div>
+
+ <div role="grid" id="grid1">
+ <div role="row">
+ <span role="gridcell">cell</span>
+ <span role="gridcell">cell</span>
+ </div>
+ <div role="row">
+ <span role="gridcell">cell</span>
+ <span role="gridcell">cell</span>
+ </div>
+ </div>
+
+ <div role="tree" id="tree1">
+ <div role="treeitem">
+ item1
+ <div role="group">
+ <div role="treeitem">item1.1</div>
+ </div>
+ </div>
+ <div>item2</div>
+ </div>
+
+ <div role="treegrid" id="treegrid1">
+ <div role="row" aria-level="1">
+ <span role="gridcell">cell</span>
+ <span role="gridcell">cell</span>
+ </div>
+ <div role="row" aria-level="2">
+ <span role="gridcell">cell</span>
+ <span role="gridcell">cell</span>
+ </div>
+ <div role="row" aria-level="1">
+ <span role="gridcell">cell</span>
+ <span role="gridcell">cell</span>
+ </div>
+ </div>
+
+ <table tabindex="0" border="2" cellspacing="0" id="grid2" role="grid"
+ aria-multiselectable="true">
+ <thead>
+ <tr>
+ <th tabindex="-1" role="columnheader" id="grid2_colhead1"
+ style="width:6em">Entry #</th>
+ <th tabindex="-1" role="columnheader" id="grid2_colhead2"
+ style="width:10em">Date</th>
+ <th tabindex="-1" role="columnheader" id="grid2_colhead3"
+ style="width:20em">Expense</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td tabindex="-1" role="rowheader" id="grid2_rowhead"
+ aria-readonly="true">1</td>
+ <td tabindex="-1" role="gridcell" id="grid2_cell1"
+ aria-selected="false">03/14/05</td>
+ <td tabindex="-1" role="gridcell" id="grid2_cell2"
+ aria-selected="false">Conference Fee</td>
+ </tr>
+ </tobdy>
+ </table>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/selectable/test_listbox.xul b/accessible/tests/mochitest/selectable/test_listbox.xul
new file mode 100644
index 000000000..990589257
--- /dev/null
+++ b/accessible/tests/mochitest/selectable/test_listbox.xul
@@ -0,0 +1,152 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<?xml-stylesheet href="../treeview.css" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL tree selectable tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../selectable.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ //gA11yEventDumpID = "debug";
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // single selectable listbox
+
+ var id = "listbox";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for list of " + id);
+
+ var select = getAccessible(id, [nsIAccessibleSelectable]);
+ testSelectableSelection(select, [ ]);
+
+ select.addItemToSelection(1);
+ testSelectableSelection(select, [ "lb1_item2" ], "addItemToSelect(1): ");
+
+ select.removeItemFromSelection(1);
+ testSelectableSelection(select, [ ],
+ "removeItemFromSelection(1): ");
+
+ todo(select.selectAll() == false,
+ "No way to select all items in listbox '" + id + "'");
+ testSelectableSelection(select, [ "lb1_item1" ], "selectAll: ");
+
+ select.addItemToSelection(1);
+ select.unselectAll();
+ testSelectableSelection(select, [ ], "unselectAll: ");
+
+ //////////////////////////////////////////////////////////////////////////
+ // multiple selectable listbox
+
+ var id = "listbox2";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for list of " + id);
+
+ var select = getAccessible(id, [nsIAccessibleSelectable]);
+ testSelectableSelection(select, [ ]);
+
+ select.addItemToSelection(1);
+ testSelectableSelection(select, [ "lb2_item2" ], "addItemToSelect(1): ");
+
+ select.removeItemFromSelection(1);
+ testSelectableSelection(select, [ ],
+ "removeItemFromSelection(1): ");
+
+ is(select.selectAll(), true,
+ "All items should be selected in listbox '" + id + "'");
+ testSelectableSelection(select, [ "lb2_item1", "lb2_item2" ],
+ "selectAll: ");
+
+ select.unselectAll();
+ testSelectableSelection(select, [ ], "unselectAll: ");
+
+ //////////////////////////////////////////////////////////////////////////
+ // listbox with headers
+
+ // XXX: addItemToSelection/removeItemFromSelection don't work correctly
+ // on listboxes with headers because header is inserted into hierarchy
+ // and child indexes that are used in these methods are shifted (see bug
+ // 591939).
+ todo(false,
+ "Fix addItemToSelection/removeItemFromSelection on listboxes with headers.");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=590176"
+ title="add pseudo SelectAccessible interface">
+ Mozilla Bug 590176
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <listbox id="listbox">
+ <listcols>
+ <listcol flex="1"/>
+ <listcol flex="1"/>
+ </listcols>
+ <listitem id="lb1_item1">
+ <listcell label="cell0"/>
+ <listcell label="cell1"/>
+ </listitem>
+ <listitem id="lb1_item2">
+ <listcell label="cell3"/>
+ <listcell label="cell4"/>
+ </listitem>
+ </listbox>
+
+ <listbox id="listbox2" seltype="multiple">
+ <listcols>
+ <listcol flex="1"/>
+ <listcol flex="1"/>
+ </listcols>
+ <listitem id="lb2_item1">
+ <listcell label="cell0"/>
+ <listcell label="cell1"/>
+ </listitem>
+ <listitem id="lb2_item2">
+ <listcell label="cell3"/>
+ <listcell label="cell4"/>
+ </listitem>
+ </listbox>
+
+ <vbox id="debug"/>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/selectable/test_menu.xul b/accessible/tests/mochitest/selectable/test_menu.xul
new file mode 100644
index 000000000..4accedbbd
--- /dev/null
+++ b/accessible/tests/mochitest/selectable/test_menu.xul
@@ -0,0 +1,78 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<?xml-stylesheet href="../treeview.css" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL tree selectable tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../selectable.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ //gA11yEventDumpID = "debug";
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // menu
+
+ var id = "menu";
+ var menu = getAccessible("menu");
+ var menuList = menu.firstChild;
+ todo(isAccessible(menuList, [nsIAccessibleSelectable]),
+ "No selectable accessible for list of menu '" + id + "'");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=590176"
+ title="add pseudo SelectAccessible interface">
+ Mozilla Bug 590176
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <menu label="menu" id="menu">
+ <menupopup>
+ <menuitem label="item1" id="m_item1"/>
+ <menuitem label="item2" id="m_item2"/>
+ </menupopup>
+ </menu>
+
+ <vbox id="debug"/>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/selectable/test_menulist.xul b/accessible/tests/mochitest/selectable/test_menulist.xul
new file mode 100644
index 000000000..5bd7a26b2
--- /dev/null
+++ b/accessible/tests/mochitest/selectable/test_menulist.xul
@@ -0,0 +1,96 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<?xml-stylesheet href="../treeview.css" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL tree selectable tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../selectable.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ //gA11yEventDumpID = "debug";
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // menulist aka combobox
+
+ var id = "combobox";
+ var combobox = getAccessible(id);
+ var comboboxList = combobox.firstChild;
+ ok(isAccessible(comboboxList, [nsIAccessibleSelectable]),
+ "No selectable accessible for list of " + id);
+
+ var select = getAccessible(comboboxList, [nsIAccessibleSelectable]);
+ testSelectableSelection(select, [ "cb1_item1" ]);
+
+ select.addItemToSelection(1);
+ testSelectableSelection(select, [ "cb1_item2" ], "addItemToSelection(1): ");
+
+ select.removeItemFromSelection(1);
+ testSelectableSelection(select, [ ],
+ "removeItemFromSelection(1): ");
+
+ is(select.selectAll(), false,
+ "No way to select all items in combobox '" + id + "'");
+ testSelectableSelection(select, [ ], "selectAll: ");
+
+ select.addItemToSelection(1);
+ select.unselectAll();
+ testSelectableSelection(select, [ ], "unselectAll: ");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=590176"
+ title="add pseudo SelectAccessible interface">
+ Mozilla Bug 590176
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <menulist id="combobox">
+ <menupopup>
+ <menuitem id="cb1_item1" label="item1"/>
+ <menuitem id="cb1_item2" label="item2"/>
+ </menupopup>
+ </menulist>
+
+ <vbox id="debug"/>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/selectable/test_select.html b/accessible/tests/mochitest/selectable/test_select.html
new file mode 100644
index 000000000..9369fdfaf
--- /dev/null
+++ b/accessible/tests/mochitest/selectable/test_select.html
@@ -0,0 +1,243 @@
+<html>
+
+<head>
+ <title>nsIAccessibleSelectable HTML select testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ </style>
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../selectable.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // select@size="1" aka combobox
+
+ var id = "combobox";
+ var combobox = getAccessible(id);
+ var comboboxList = combobox.firstChild;
+ ok(isAccessible(comboboxList, [nsIAccessibleSelectable]),
+ "No selectable accessible for list of " + id);
+
+ var select = getAccessible(comboboxList, [nsIAccessibleSelectable]);
+ testSelectableSelection(select, [ "cb1_item1" ]);
+
+ // select 2nd item
+ select.addItemToSelection(1);
+ testSelectableSelection(select, [ "cb1_item2" ], "addItemToSelection(1): ");
+
+ // unselect 2nd item, 1st item gets selected automatically
+ select.removeItemFromSelection(1);
+ testSelectableSelection(select, [ "cb1_item1" ],
+ "removeItemFromSelection(1): ");
+
+ // doesn't change selection
+ is(select.selectAll(), false,
+ "No way to select all items in combobox '" + id + "'");
+ testSelectableSelection(select, [ "cb1_item1" ], "selectAll: ");
+
+ // doesn't change selection
+ select.unselectAll();
+ testSelectableSelection(select, [ "cb1_item1" ], "unselectAll: ");
+
+ //////////////////////////////////////////////////////////////////////////
+ // select@size="1" with optgroups
+
+ id = "combobox2";
+ combobox = getAccessible(id);
+ comboboxList = combobox.firstChild;
+ ok(isAccessible(comboboxList, [nsIAccessibleSelectable]),
+ "No selectable accessible for list of " + id);
+
+ select = getAccessible(comboboxList, [nsIAccessibleSelectable]);
+ testSelectableSelection(select, [ "cb2_item1" ]);
+
+ select.addItemToSelection(1);
+ testSelectableSelection(select, [ "cb2_item2" ]);
+
+ select.removeItemFromSelection(1);
+ testSelectableSelection(select, [ "cb2_item1" ]);
+
+ is(select.selectAll(), false,
+ "No way to select all items in combobox " + id + "'");
+ testSelectableSelection(select, [ "cb2_item1" ]);
+
+ select.unselectAll();
+ testSelectableSelection(select, [ "cb2_item1" ]);
+
+ //////////////////////////////////////////////////////////////////////////
+ // select@size="4" aka single selectable listbox
+
+ var id = "listbox";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for " + id);
+
+ select = getAccessible(id, [nsIAccessibleSelectable]);
+ testSelectableSelection(select, [ ]);
+
+ // select 2nd item
+ select.addItemToSelection(1);
+ testSelectableSelection(select, [ "lb1_item2" ], "addItemToSelection(1): ");
+
+ // unselect 2nd item, 1st item gets selected automatically
+ select.removeItemFromSelection(1);
+ testSelectableSelection(select, [ ],
+ "removeItemFromSelection(1): ");
+
+ // doesn't change selection
+ is(select.selectAll(), false,
+ "No way to select all items in single selectable listbox '" + id + "'");
+ testSelectableSelection(select, [ ], "selectAll: ");
+
+ // doesn't change selection
+ select.unselectAll();
+ testSelectableSelection(select, [ ], "unselectAll: ");
+
+ //////////////////////////////////////////////////////////////////////////
+ // select@size="4" with optgroups, single selectable
+
+ id = "listbox2";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for " + id);
+
+ select = getAccessible(id, [nsIAccessibleSelectable]);
+ testSelectableSelection(select, [ ]);
+
+ select.addItemToSelection(1);
+ testSelectableSelection(select, [ "lb2_item2" ]);
+
+ select.removeItemFromSelection(1);
+ testSelectableSelection(select, [ ]);
+
+ is(select.selectAll(), false,
+ "No way to select all items in single selectable listbox " + id + "'");
+ testSelectableSelection(select, [ ]);
+
+ select.unselectAll();
+ testSelectableSelection(select, [ ]);
+
+ //////////////////////////////////////////////////////////////////////////
+ // select@size="4" multiselect aka listbox
+
+ id = "listbox3";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for " + id);
+
+ select = getAccessible(id, [nsIAccessibleSelectable]);
+ testSelectableSelection(select, [ ]);
+
+ select.addItemToSelection(0);
+ testSelectableSelection(select, [ "lb3_item1" ], "addItemToSelection: ");
+
+ select.removeItemFromSelection(0);
+ testSelectableSelection(select, [ ], "removeItemFromSelection: ");
+
+ is(select.selectAll(), true,
+ "All items in listbox '" + id + "' should be selected");
+ testSelectableSelection(select, [ "lb3_item1", "lb3_item2"],
+ "selectAll: ");
+
+ select.unselectAll();
+ testSelectableSelection(select, [ ], "unselectAll: ");
+
+ //////////////////////////////////////////////////////////////////////////
+ // select@size="4" multiselect with optgroups
+
+ var id = "listbox4";
+ ok(isAccessible(id, [nsIAccessibleSelectable]),
+ "No selectable accessible for " + id);
+
+ select = getAccessible(id, [nsIAccessibleSelectable]);
+ testSelectableSelection(select, [ ]);
+
+ select.addItemToSelection(0);
+ testSelectableSelection(select, [ "lb4_item1" ]);
+
+ select.removeItemFromSelection(0);
+ testSelectableSelection(select, [ ]);
+
+ is(select.selectAll(), true,
+ "All items in listbox '" + id + "' should be selected");
+ testSelectableSelection(select, [ "lb4_item1", "lb4_item2"]);
+
+ select.unselectAll();
+ testSelectableSelection(select, [ ]);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=530014"
+ title="ARIA single selectable widget should implement nsIAccessibleSelectable">
+ Mozilla Bug 530014
+ </a><br>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=590176"
+ title="add pseudo SelectAccessible interface">
+ Mozilla Bug 590176
+ </a><br>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <select id="combobox">
+ <option id="cb1_item1">option1</option>
+ <option id="cb1_item2">option2</option>
+ </select>
+
+ <select id="combobox2">
+ <option id="cb2_item1">option1</option>
+ <optgroup>optgroup
+ <option id="cb2_item2">option2</option>
+ </optgroup>
+ </select>
+
+ <select id="listbox" size="4">
+ <option id="lb1_item1">option1</option>
+ <option id="lb1_item2">option2</option>
+ </select>
+
+ <select id="listbox2" size="4">
+ <option id="lb2_item1">option1</option>
+ <optgroup>optgroup>
+ <option id="lb2_item2">option2</option>
+ </optgroup>
+ </select>
+
+ <select id="listbox3" size="4" multiple="true">
+ <option id="lb3_item1">option1</option>
+ <option id="lb3_item2">option2</option>
+ </select>
+
+ <select id="listbox4" size="4" multiple="true">
+ <option id="lb4_item1">option1</option>
+ <optgroup>optgroup>
+ <option id="lb4_item2">option2</option>
+ </optgroup>
+ </select>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/selectable/test_tree.xul b/accessible/tests/mochitest/selectable/test_tree.xul
new file mode 100644
index 000000000..81e082534
--- /dev/null
+++ b/accessible/tests/mochitest/selectable/test_tree.xul
@@ -0,0 +1,188 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<?xml-stylesheet href="../treeview.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL tree selectable tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../selectable.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "debug";
+
+ /**
+ * Event queue invoker object to test accessible states for XUL tree
+ * accessible.
+ */
+ function statesChecker(aTreeID, aView)
+ {
+ this.DOMNode = getNode(aTreeID);
+
+ this.invoke = function invoke()
+ {
+ this.DOMNode.view = aView;
+ }
+ this.check = function check()
+ {
+ var tree = getAccessible(this.DOMNode);
+
+ var isTreeMultiSelectable = false;
+ var seltype = this.DOMNode.getAttribute("seltype");
+ if (seltype != "single" && seltype != "cell" && seltype != "text")
+ isTreeMultiSelectable = true;
+
+ // selectAll
+ var accSelectable = getAccessible(this.DOMNode,
+ [nsIAccessibleSelectable]);
+ ok(accSelectable, "tree is not selectable!");
+ if (accSelectable) {
+ is(accSelectable.selectAll(), isTreeMultiSelectable,
+ "SelectAll is not correct for seltype: " + seltype);
+ }
+
+ var selectedChildren = [];
+ if (isTreeMultiSelectable) {
+ var rows = tree.children;
+ for (var i = 0; i < rows.length; i++) {
+ var row = rows.queryElementAt(i, nsIAccessible);
+ if (getRole(row) == ROLE_OUTLINEITEM || getRole(row) == ROLE_ROW)
+ selectedChildren.push(row);
+ }
+ }
+ testSelectableSelection(accSelectable, selectedChildren,
+ "selectAll test. ");
+
+ // unselectAll
+ accSelectable.unselectAll();
+ testSelectableSelection(accSelectable, [], "unselectAll test. ");
+
+ // addItemToSelection
+ accSelectable.addItemToSelection(1);
+ accSelectable.addItemToSelection(3);
+
+ selectedChildren = isTreeMultiSelectable ?
+ [ accSelectable.getChildAt(2), accSelectable.getChildAt(4) ] :
+ [ accSelectable.getChildAt(2) ];
+ testSelectableSelection(accSelectable, selectedChildren,
+ "addItemToSelection test. ");
+
+ // removeItemFromSelection
+ accSelectable.removeItemFromSelection(1);
+
+ selectedChildren = isTreeMultiSelectable ?
+ [ accSelectable.getChildAt(4) ] : [ ];
+ testSelectableSelection(accSelectable, selectedChildren,
+ "removeItemFromSelection test. ");
+ }
+
+ this.getID = function getID()
+ {
+ "tree processor for " + prettyName(aTreeID);
+ }
+ }
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ gQueue = new eventQueue(EVENT_REORDER);
+ gQueue.push(new statesChecker("tree", new nsTreeTreeView()));
+ gQueue.push(new statesChecker("treesingle", new nsTreeTreeView()));
+ gQueue.push(new statesChecker("treecell", new nsTreeTreeView()));
+ gQueue.push(new statesChecker("treetext", new nsTreeTreeView()));
+ gQueue.push(new statesChecker("tabletree", new nsTreeTreeView()));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=523118"
+ title="we mistake 'cell' and text' xul tree seltypes for multiselects">
+ Mozilla Bug 523118
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=624977"
+ title="Optimize nsXulTreeAccessible selectedItems()">
+ Mozilla Bug 624977
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <tree id="treesingle" flex="1" seltype="single">
+ <treecols>
+ <treecol id="col_single" flex="1" primary="true" label="column"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <tree id="treecell" flex="1" seltype="cell">
+ <treecols>
+ <treecol id="col_cell" flex="1" primary="true" label="column"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <tree id="treetext" flex="1" seltype="text">
+ <treecols>
+ <treecol id="col_text" flex="1" primary="true" label="column"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <tree id="tabletree" flex="1" editable="true">
+ <treecols>
+ <treecol id="tabletree_col1" cycler="true" label="cycler"/>
+ <treecol id="tabletree_col2" flex="1" primary="true" label="column1"/>
+ <treecol id="tabletree_col3" flex="1" label="column2"/>
+ <treecol id="tabletree_col4" flex="1" label="checker"
+ type="checkbox" editable="true"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <vbox id="debug"/>
+ </vbox>
+ </hbox>
+
+</window>
diff --git a/accessible/tests/mochitest/states.js b/accessible/tests/mochitest/states.js
new file mode 100644
index 000000000..73ec126e3
--- /dev/null
+++ b/accessible/tests/mochitest/states.js
@@ -0,0 +1,266 @@
+////////////////////////////////////////////////////////////////////////////////
+// Helper functions for accessible states testing.
+//
+// requires:
+// common.js
+// role.js
+//
+////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////
+// State constants
+
+// const STATE_BUSY is defined in common.js
+const STATE_CHECKED = nsIAccessibleStates.STATE_CHECKED;
+const STATE_CHECKABLE = nsIAccessibleStates.STATE_CHECKABLE;
+const STATE_COLLAPSED = nsIAccessibleStates.STATE_COLLAPSED;
+const STATE_DEFAULT = nsIAccessibleStates.STATE_DEFAULT;
+const STATE_EXPANDED = nsIAccessibleStates.STATE_EXPANDED;
+const STATE_EXTSELECTABLE = nsIAccessibleStates.STATE_EXTSELECTABLE;
+const STATE_FLOATING = nsIAccessibleStates.STATE_FLOATING;
+const STATE_FOCUSABLE = nsIAccessibleStates.STATE_FOCUSABLE;
+const STATE_FOCUSED = nsIAccessibleStates.STATE_FOCUSED;
+const STATE_HASPOPUP = nsIAccessibleStates.STATE_HASPOPUP;
+const STATE_INVALID = nsIAccessibleStates.STATE_INVALID;
+const STATE_INVISIBLE = nsIAccessibleStates.STATE_INVISIBLE;
+const STATE_LINKED = nsIAccessibleStates.STATE_LINKED;
+const STATE_MIXED = nsIAccessibleStates.STATE_MIXED;
+const STATE_MULTISELECTABLE = nsIAccessibleStates.STATE_MULTISELECTABLE;
+const STATE_OFFSCREEN = nsIAccessibleStates.STATE_OFFSCREEN;
+const STATE_PRESSED = nsIAccessibleStates.STATE_PRESSED;
+const STATE_PROTECTED = nsIAccessibleStates.STATE_PROTECTED;
+const STATE_READONLY = nsIAccessibleStates.STATE_READONLY;
+const STATE_REQUIRED = nsIAccessibleStates.STATE_REQUIRED;
+const STATE_SELECTABLE = nsIAccessibleStates.STATE_SELECTABLE;
+const STATE_SELECTED = nsIAccessibleStates.STATE_SELECTED;
+const STATE_TRAVERSED = nsIAccessibleStates.STATE_TRAVERSED;
+const STATE_UNAVAILABLE = nsIAccessibleStates.STATE_UNAVAILABLE;
+
+const EXT_STATE_ACTIVE = nsIAccessibleStates.EXT_STATE_ACTIVE;
+const EXT_STATE_DEFUNCT = nsIAccessibleStates.EXT_STATE_DEFUNCT;
+const EXT_STATE_EDITABLE = nsIAccessibleStates.EXT_STATE_EDITABLE;
+const EXT_STATE_ENABLED = nsIAccessibleStates.EXT_STATE_ENABLED;
+const EXT_STATE_EXPANDABLE = nsIAccessibleStates.EXT_STATE_EXPANDABLE;
+const EXT_STATE_HORIZONTAL = nsIAccessibleStates.EXT_STATE_HORIZONTAL;
+const EXT_STATE_MODAL = nsIAccessibleStates.EXT_STATE_MODAL;
+const EXT_STATE_MULTI_LINE = nsIAccessibleStates.EXT_STATE_MULTI_LINE;
+const EXT_STATE_PINNED = nsIAccessibleStates.EXT_STATE_PINNED;
+const EXT_STATE_SENSITIVE = nsIAccessibleStates.EXT_STATE_SENSITIVE;
+const EXT_STATE_SINGLE_LINE = nsIAccessibleStates.EXT_STATE_SINGLE_LINE;
+const EXT_STATE_STALE = nsIAccessibleStates.EXT_STATE_STALE;
+const EXT_STATE_SUPPORTS_AUTOCOMPLETION =
+ nsIAccessibleStates.EXT_STATE_SUPPORTS_AUTOCOMPLETION;
+const EXT_STATE_VERTICAL = nsIAccessibleStates.EXT_STATE_VERTICAL;
+
+const kOrdinalState = false;
+const kExtraState = 1;
+
+////////////////////////////////////////////////////////////////////////////////
+// Test functions
+
+/**
+ * Tests the states and extra states of the given accessible.
+ * Also tests for unwanted states and extra states.
+ * In addition, the function performs a few plausibility checks derived from the
+ * sstates and extra states passed in.
+ *
+ * @param aAccOrElmOrID The accessible, DOM element or ID to be tested.
+ * @param aState The state bits that are wanted.
+ * @param aExtraState The extra state bits that are wanted.
+ * @param aAbsentState State bits that are not wanted.
+ * @param aAbsentExtraState Extra state bits that are not wanted.
+ * @param aTestName The test name.
+ */
+function testStates(aAccOrElmOrID, aState, aExtraState, aAbsentState,
+ aAbsentExtraState, aTestName)
+{
+ var [state, extraState] = getStates(aAccOrElmOrID);
+ var role = getRole(aAccOrElmOrID);
+ var id = prettyName(aAccOrElmOrID) + (aTestName ? " [" + aTestName + "]": "");
+
+ // Primary test.
+ if (aState) {
+ isState(state & aState, aState, false,
+ "wrong state bits for " + id + "!");
+ }
+
+ if (aExtraState)
+ isState(extraState & aExtraState, aExtraState, true,
+ "wrong extra state bits for " + id + "!");
+
+ if (aAbsentState)
+ isState(state & aAbsentState, 0, false,
+ "state bits should not be present in ID " + id + "!");
+
+ if (aAbsentExtraState)
+ isState(extraState & aAbsentExtraState, 0, true,
+ "extraState bits should not be present in ID " + id + "!");
+
+ // Additional test.
+
+ // focused/focusable
+ if (state & STATE_FOCUSED)
+ isState(state & STATE_FOCUSABLE, STATE_FOCUSABLE, false,
+ "Focussed " + id + " must be focusable!");
+
+ if (aAbsentState && (aAbsentState & STATE_FOCUSABLE)) {
+ isState(state & STATE_FOCUSED, 0, false,
+ "Not focusable " + id + " must be not focused!");
+ }
+
+ // multiline/singleline
+ if (extraState & EXT_STATE_MULTI_LINE)
+ isState(extraState & EXT_STATE_SINGLE_LINE, 0, true,
+ "Multiline " + id + " cannot be singleline!");
+
+ if (extraState & EXT_STATE_SINGLE_LINE)
+ isState(extraState & EXT_STATE_MULTI_LINE, 0, true,
+ "Singleline " + id + " cannot be multiline!");
+
+ // expanded/collapsed/expandable
+ if (state & STATE_COLLAPSED || state & STATE_EXPANDED)
+ isState(extraState & EXT_STATE_EXPANDABLE, EXT_STATE_EXPANDABLE, true,
+ "Collapsed or expanded " + id + " must be expandable!");
+
+ if (state & STATE_COLLAPSED)
+ isState(state & STATE_EXPANDED, 0, false,
+ "Collapsed " + id + " cannot be expanded!");
+
+ if (state & STATE_EXPANDED)
+ isState(state & STATE_COLLAPSED, 0, false,
+ "Expanded " + id + " cannot be collapsed!");
+
+ if (aAbsentState && (extraState & EXT_STATE_EXPANDABLE)) {
+ if (aAbsentState & STATE_EXPANDED) {
+ isState(state & STATE_COLLAPSED, STATE_COLLAPSED, false,
+ "Not expanded " + id + " must be collapsed!");
+ } else if (aAbsentState & STATE_COLLAPSED) {
+ isState(state & STATE_EXPANDED, STATE_EXPANDED, false,
+ "Not collapsed " + id + " must be expanded!");
+ }
+ }
+
+ // checked/mixed/checkable
+ if (state & STATE_CHECKED || state & STATE_MIXED &&
+ role != ROLE_TOGGLE_BUTTON && role != ROLE_PROGRESSBAR)
+ isState(state & STATE_CHECKABLE, STATE_CHECKABLE, false,
+ "Checked or mixed element must be checkable!");
+
+ if (state & STATE_CHECKED)
+ isState(state & STATE_MIXED, 0, false,
+ "Checked element cannot be state mixed!");
+
+ if (state & STATE_MIXED)
+ isState(state & STATE_CHECKED, 0, false,
+ "Mixed element cannot be state checked!");
+
+ // selected/selectable
+ if (state & STATE_SELECTED) {
+ isState(state & STATE_SELECTABLE, STATE_SELECTABLE, false,
+ "Selected element must be selectable!");
+ }
+}
+
+/**
+ * Tests an acessible and its sub tree for the passed in state bits.
+ * Used to make sure that states are propagated to descendants, for example the
+ * STATE_UNAVAILABLE from a container to its children.
+ *
+ * @param aAccOrElmOrID The accessible, DOM element or ID to be tested.
+ * @param aState The state bits that are wanted.
+ * @param aExtraState The extra state bits that are wanted.
+ * @param aAbsentState State bits that are not wanted.
+ */
+function testStatesInSubtree(aAccOrElmOrID, aState, aExtraState, aAbsentState)
+{
+ // test accessible and its subtree for propagated states.
+ var acc = getAccessible(aAccOrElmOrID);
+ if (!acc)
+ return;
+
+ if (getRole(acc) != ROLE_TEXT_LEAF)
+ // Right now, text leafs don't get tested because the states are not being
+ // propagated.
+ testStates(acc, aState, aExtraState, aAbsentState);
+
+ // Iterate over its children to see if the state got propagated.
+ var children = null;
+ try {
+ children = acc.children;
+ } catch(e) {}
+ ok(children, "Could not get children for " + aAccOrElmOrID +"!");
+
+ if (children) {
+ for (var i = 0; i < children.length; i++) {
+ var childAcc = children.queryElementAt(i, nsIAccessible);
+ testStatesInSubtree(childAcc, aState, aExtraState, aAbsentState);
+ }
+ }
+}
+
+/**
+ * Fails if no defunct state on the accessible.
+ */
+function testIsDefunct(aAccessible, aTestName)
+{
+ var id = prettyName(aAccessible) + (aTestName ? " [" + aTestName + "]" : "");
+ var [state, extraState] = getStates(aAccessible);
+ isState(extraState & EXT_STATE_DEFUNCT, EXT_STATE_DEFUNCT, true,
+ "no defuct state for " + id + "!");
+}
+
+function getStringStates(aAccOrElmOrID)
+{
+ var [state, extraState] = getStates(aAccOrElmOrID);
+ return statesToString(state, extraState);
+}
+
+function getStates(aAccOrElmOrID)
+{
+ var acc = getAccessible(aAccOrElmOrID);
+ if (!acc)
+ return [0, 0];
+
+ var state = {}, extraState = {};
+ acc.getState(state, extraState);
+
+ return [state.value, extraState.value];
+}
+
+/**
+ * Return true if the accessible has given states.
+ */
+function hasState(aAccOrElmOrID, aState, aExtraState)
+{
+ var [state, exstate] = getStates(aAccOrElmOrID);
+ return (aState ? state & aState : true) &&
+ (aExtraState ? exstate & aExtraState : true);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Private implementation details
+
+/**
+ * Analogy of SimpleTest.is function used to compare states.
+ */
+function isState(aState1, aState2, aIsExtraStates, aMsg)
+{
+ if (aState1 == aState2) {
+ ok(true, aMsg);
+ return;
+ }
+
+ var got = "0";
+ if (aState1) {
+ got = statesToString(aIsExtraStates ? 0 : aState1,
+ aIsExtraStates ? aState1 : 0);
+ }
+
+ var expected = "0";
+ if (aState2) {
+ expected = statesToString(aIsExtraStates ? 0 : aState2,
+ aIsExtraStates ? aState2 : 0);
+ }
+
+ ok(false, aMsg + "got '" + got + "', expected '" + expected + "'");
+}
diff --git a/accessible/tests/mochitest/states/a11y.ini b/accessible/tests/mochitest/states/a11y.ini
new file mode 100644
index 000000000..f07afc3e9
--- /dev/null
+++ b/accessible/tests/mochitest/states/a11y.ini
@@ -0,0 +1,37 @@
+[DEFAULT]
+support-files =
+ z_frames.html
+ z_frames_article.html
+ z_frames_checkbox.html
+ z_frames_textbox.html
+ z_frames_update.html
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/dumbfile.zip
+ !/accessible/tests/mochitest/formimage.png
+ !/accessible/tests/mochitest/treeview.css
+
+[test_aria.html]
+[test_aria.xul]
+[test_aria_imgmap.html]
+[test_aria_widgetitems.html]
+[test_buttons.html]
+[test_controls.html]
+[test_controls.xul]
+[test_doc.html]
+[test_doc_busy.html]
+skip-if = os == 'mac' && os_version == '10.6'
+[test_docarticle.html]
+[test_editablebody.html]
+[test_expandable.xul]
+[test_frames.html]
+[test_inputs.html]
+[test_link.html]
+[test_popup.xul]
+[test_selects.html]
+[test_stale.html]
+[test_tabs.xul]
+[test_textbox.xul]
+[test_tree.xul]
+[test_visibility.html]
+[test_visibility.xul]
+skip-if = buildapp == "mulet"
diff --git a/accessible/tests/mochitest/states/test_aria.html b/accessible/tests/mochitest/states/test_aria.html
new file mode 100644
index 000000000..7d1ecf650
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_aria.html
@@ -0,0 +1,629 @@
+<html>
+
+<head>
+ <title>ARIA based nsIAccessible states testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <style type="text/css">
+ .offscreen {
+ position: absolute;
+ left: -5000px;
+ top: -5000px;
+ height: 100px;
+ width: 100px;
+ }
+ </style>
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ function testAriaDisabledTree(aAccOrElmOrID)
+ {
+ // test accessible and its subtree for propagated state.
+ var acc = getAccessible(aAccOrElmOrID);
+ if (!acc)
+ return;
+
+ var [state, extraState] = getStates(aAccOrElmOrID);
+ if (state & STATE_UNAVAILABLE) {
+ var role = getRole(acc);
+ if (role != ROLE_GROUPING) {
+ testStates(acc, STATE_FOCUSABLE);
+ }
+ }
+
+ // Iterate over its children to see if the state got propagated.
+ var children = null;
+ try {
+ children = acc.children;
+ } catch(e) {}
+ ok(children, "Could not get children for " + aAccOrElmOrID +"!");
+
+ if (children) {
+ for (var i = 0; i < children.length; i++) {
+ var childAcc = children.queryElementAt(i, nsIAccessible);
+ testAriaDisabledTree(childAcc);
+ }
+ }
+ }
+
+ function doTest()
+ {
+ // aria_autocomplete
+ testStates("textbox_autocomplete_inline", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("textbox_autocomplete_list", STATE_HASPOPUP, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("textbox_autocomplete_both", STATE_HASPOPUP, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("combobox_autocomplete_inline", STATE_HASPOPUP, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("combobox_autocomplete_list", STATE_HASPOPUP, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("combobox_autocomplete_both", STATE_HASPOPUP, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+
+ testStates("htmltext_autocomplete_list", STATE_HASPOPUP, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("htmltextarea_autocomplete_list", STATE_HASPOPUP, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+
+ // aria-busy
+ testStates("textbox_busy_false", 0, 0, STATE_BUSY);
+ testStates("textbox_busy_true", STATE_BUSY);
+ testStates("textbox_busy_error", STATE_INVALID);
+
+ // aria-expanded
+ testStates("combobox", STATE_COLLAPSED);
+ testStates("combobox_expanded", STATE_EXPANDED);
+
+ // tri-state checkbox
+ var checkboxElem = getNode("check1");
+ if (checkboxElem) {
+ testStates(checkboxElem, STATE_CHECKED);
+ checkboxElem.checked = false;
+ testStates(checkboxElem, 0, 0, STATE_CHECKED);
+ checkboxElem.indeterminate = true;
+ testStates(checkboxElem, STATE_MIXED, 0);
+ }
+
+ // aria-checked
+ testStates("aria_checked_checkbox", STATE_CHECKED);
+ testStates("aria_mixed_checkbox", STATE_MIXED);
+ testStates("aria_checked_switch", STATE_CHECKED);
+ testStates("aria_mixed_switch", 0, 0, STATE_MIXED); // unsupported
+
+ // test disabled group and all its descendants to see if they are
+ // disabled, too. See bug 429285.
+ testAriaDisabledTree("group");
+
+ // aria-modal
+ testStates("aria_modal", 0, EXT_STATE_MODAL);
+ testStates("aria_modal_false", 0, 0, 0, EXT_STATE_MODAL);
+
+ // aria-multiline
+ testStates("aria_multiline_textbox", 0, EXT_STATE_MULTI_LINE);
+
+ // aria-multiselectable
+ testStates("aria_multiselectable_listbox",
+ STATE_MULTISELECTABLE | STATE_EXTSELECTABLE);
+
+ // aria-pressed
+ testStates("aria_pressed_button", STATE_PRESSED, 0, STATE_CHECKABLE);
+ testStates("aria_pressed_native_button", STATE_PRESSED, 0, STATE_CHECKABLE);
+
+ // aria-readonly
+ testStates("aria_readonly_textbox", STATE_READONLY);
+
+ // readonly/editable on grid and gridcell
+ testStates("aria_grid_default", 0, EXT_STATE_EDITABLE,
+ STATE_READONLY, 0);
+ testStates("aria_grid_default_colheader_readonly", STATE_READONLY, 0,
+ 0, EXT_STATE_EDITABLE);
+ testStates("aria_grid_default_colheader_inherited", 0, EXT_STATE_EDITABLE,
+ STATE_READONLY, 0);
+ testStates("aria_grid_default_rowheader_readonly", STATE_READONLY, 0,
+ 0, EXT_STATE_EDITABLE);
+ testStates("aria_grid_default_rowheader_inherited", 0, EXT_STATE_EDITABLE,
+ STATE_READONLY, 0);
+ testStates("aria_grid_default_cell_readonly", STATE_READONLY, 0,
+ 0, EXT_STATE_EDITABLE);
+ testStates("aria_grid_default_cell_inherited", 0, EXT_STATE_EDITABLE,
+ STATE_READONLY, 0);
+
+ testStates("aria_grid_readonly", STATE_READONLY, 0,
+ 0, EXT_STATE_EDITABLE);
+ testStates("aria_grid_readonly_colheader_editable", 0, EXT_STATE_EDITABLE,
+ STATE_READONLY, 0);
+ testStates("aria_grid_readonly_colheader_inherited", STATE_READONLY, 0,
+ 0, EXT_STATE_EDITABLE);
+ testStates("aria_grid_readonly_rowheader_editable", 0, EXT_STATE_EDITABLE,
+ STATE_READONLY, 0);
+ testStates("aria_grid_readonly_rowheader_inherited", STATE_READONLY, 0,
+ 0, EXT_STATE_EDITABLE);
+ testStates("aria_grid_readonly_cell_editable", 0, EXT_STATE_EDITABLE,
+ STATE_READONLY, 0);
+ testStates("aria_grid_readonly_cell_inherited", STATE_READONLY, 0,
+ 0, EXT_STATE_EDITABLE);
+
+ // readonly/editable on treegrid and gridcell
+ testStates("aria_treegrid_default", 0, EXT_STATE_EDITABLE,
+ STATE_READONLY, 0);
+ testStates("aria_treegrid_default_colheader_readonly", STATE_READONLY, 0,
+ 0, EXT_STATE_EDITABLE);
+ testStates("aria_treegrid_default_colheader_inherited", 0, EXT_STATE_EDITABLE,
+ STATE_READONLY, 0);
+ testStates("aria_treegrid_default_rowheader_readonly", STATE_READONLY, 0,
+ 0, EXT_STATE_EDITABLE);
+ testStates("aria_treegrid_default_rowheader_inherited", 0, EXT_STATE_EDITABLE,
+ STATE_READONLY, 0);
+ testStates("aria_treegrid_default_cell_readonly", STATE_READONLY, 0,
+ 0, EXT_STATE_EDITABLE);
+ testStates("aria_treegrid_default_cell_inherited", 0, EXT_STATE_EDITABLE,
+ STATE_READONLY, 0);
+
+ testStates("aria_treegrid_readonly", STATE_READONLY, 0,
+ 0, EXT_STATE_EDITABLE);
+ testStates("aria_treegrid_readonly_colheader_editable", 0, EXT_STATE_EDITABLE,
+ STATE_READONLY, 0);
+ testStates("aria_treegrid_readonly_colheader_inherited", STATE_READONLY, 0,
+ 0, EXT_STATE_EDITABLE);
+ testStates("aria_treegrid_readonly_rowheader_editable", 0, EXT_STATE_EDITABLE,
+ STATE_READONLY, 0);
+ testStates("aria_treegrid_readonly_rowheader_inherited", STATE_READONLY, 0,
+ 0, EXT_STATE_EDITABLE);
+ testStates("aria_treegrid_readonly_cell_editable", 0, EXT_STATE_EDITABLE,
+ STATE_READONLY, 0);
+ testStates("aria_treegrid_readonly_cell_inherited", STATE_READONLY, 0,
+ 0, EXT_STATE_EDITABLE);
+
+ // aria-selectable
+ testStates("aria_selectable_listitem", STATE_SELECTABLE | STATE_SELECTED);
+
+ // active state caused by aria-activedescendant
+ testStates("as_item1", 0, EXT_STATE_ACTIVE);
+ testStates("as_item2", 0, 0, 0, EXT_STATE_ACTIVE);
+
+ // universal ARIA properties inherited from file input control
+ var fileBrowseButton = getAccessible("fileinput").firstChild;
+ testStates(fileBrowseButton,
+ STATE_BUSY | STATE_UNAVAILABLE | STATE_REQUIRED | STATE_HASPOPUP | STATE_INVALID);
+ // No states on the label.
+
+ // offscreen test
+ testStates("aria_offscreen_textbox", STATE_OFFSCREEN);
+
+ //
+ // This section tests aria roles on links/anchors for underlying
+ // HTMLLinkAccessible creation. (see closed bug 494807)
+ //
+
+ // strong roles
+ testStates("aria_menuitem_link", 0, 0, STATE_LINKED);
+ testStates("aria_button_link", 0, 0, STATE_LINKED);
+ testStates("aria_checkbox_link", 0, 0, STATE_LINKED);
+
+ // strong landmark
+ testStates("aria_application_link", 0, 0, STATE_LINKED);
+ testStates("aria_application_anchor", 0, 0, STATE_SELECTABLE);
+
+ // strange cases
+ testStates("aria_link_link", STATE_LINKED);
+ testStates("aria_link_anchor", STATE_SELECTABLE);
+
+ // some weak landmarks
+ testStates("aria_main_link", STATE_LINKED);
+ testStates("aria_navigation_link", STATE_LINKED);
+ testStates("aria_main_anchor", STATE_SELECTABLE);
+ testStates("aria_navigation_anchor", STATE_SELECTABLE);
+
+ // aria-orientation
+ testStates("aria_combobox", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_hcombobox", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_vcombobox", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_listbox", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_hlistbox", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_vlistbox", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_menu", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_hmenu", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_vmenu", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_menubar", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_hmenubar", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_vmenubar", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_radiogroup", 0, 0, 0, EXT_STATE_HORIZONTAL | EXT_STATE_VERTICAL);
+ testStates("aria_hradiogroup", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_vradiogroup", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_scrollbar", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_hscrollbar", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_vscrollbar", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_separator", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_hseparator", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_vseparator", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_slider", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_hslider", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_vslider", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_tablist", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_htablist", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_vtablist", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_toolbar", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_htoolbar", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_vtoolbar", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_tree", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_htree", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_vtree", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_treegrid", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+ testStates("aria_htreegrid", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
+ testStates("aria_vtreegrid", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+
+ // indeterminate ARIA progressbars (no aria-valuenow or aria-valuetext attribute)
+ // should expose mixed state
+ testStates("aria_progressbar", STATE_MIXED);
+ testStates("aria_progressbar_valuenow", 0, 0, STATE_MIXED);
+ testStates("aria_progressbar_valuetext", 0, 0, STATE_MIXED);
+
+ testStates("aria_listbox", STATE_FOCUSABLE);
+ testStates("aria_grid", STATE_FOCUSABLE);
+ testStates("aria_tree", STATE_FOCUSABLE);
+ testStates("aria_treegrid", STATE_FOCUSABLE);
+ testStates("aria_listbox_disabled", 0, 0, STATE_FOCUSABLE);
+ testStates("aria_grid_disabled", 0, 0, STATE_FOCUSABLE);
+ testStates("aria_tree_disabled", 0, 0, STATE_FOCUSABLE);
+ testStates("aria_treegrid_disabled", 0, 0, STATE_FOCUSABLE);
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=457219"
+ title="nsIAccessible states testing">
+ Mozilla Bug 457219
+ </a><br />
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=429285"
+ title="Propagate aria-disabled to descendants">
+ Mozilla Bug 429285
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=457226"
+ title="Mochitests for ARIA states">
+ Mozilla Bug 457226
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=499653"
+ title="Unify ARIA state attributes mapping rules">
+ Mozilla Bug 499653
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=681674"
+ title="aria-autocomplete not supported on standard form text input controls">
+ Mozilla Bug 681674
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=681674"
+ title="aria-orientation should be applied to separator and slider roles">
+ Mozilla Bug 681674
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=689847"
+ title="Expose active state on current item of selectable widgets">
+ Mozilla Bug 689847
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=699017"
+ title="File input control should be propogate states to descendants">
+ Mozilla Bug 699017
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=690199"
+ title="ARIA select widget should expose focusable state regardless the way they manage its children">
+ Mozilla Bug 690199
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=740851"
+ title="ARIA undetermined progressmeters should expose mixed state">
+ Mozilla Bug 740851
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=762876"
+ title="fix default horizontal / vertical state of role=scrollbar and ensure only one of horizontal / vertical states is exposed">
+ Mozilla Bug 762876
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=892091"
+ title="ARIA treegrid should be editable by default">
+ Bug 892091
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=835121"
+ title="ARIA grid should be editable by default">
+ Mozilla Bug 835121
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=989958"
+ title="Pressed state is not exposed on a button element with aria-pressed attribute">
+ Mozilla Bug 989958
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1136563"
+ title="Support ARIA 1.1 switch role">
+ Mozilla Bug 1136563
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="textbox_autocomplete_inline" role="textbox" aria-autocomplete="inline"></div>
+ <div id="textbox_autocomplete_list" role="textbox" aria-autocomplete="list"></div>
+ <div id="textbox_autocomplete_both" role="textbox" aria-autocomplete="both"></div>
+ <div id="combobox_autocomplete_inline" role="combobox" aria-autocomplete="inline"></div>
+ <div id="combobox_autocomplete_list" role="combobox" aria-autocomplete="list"></div>
+ <div id="combobox_autocomplete_both" role="combobox" aria-autocomplete="both"></div>
+
+ <input id="htmltext_autocomplete_list" type="text" aria-autocomplete="list" />
+ <textarea id="htmltextarea_autocomplete_list" aria-autocomplete="list"></textarea>
+
+ <div id="textbox_busy_false" role="textbox" aria-busy="false"></div>
+ <div id="textbox_busy_true" role="textbox" aria-busy="true"></div>
+ <div id="textbox_busy_error" role="textbox" aria-busy="error"></div>
+
+ <div id="combobox" role="combobox">combobox</div>
+ <div id="combobox_expanded" role="combobox"
+ aria-expanded="true">combobox</div>
+
+ <input type="checkbox" id="check1" value="I agree" checked="true"/>
+
+ <div id="aria_checked_checkbox" role="checkbox" aria-checked="true">
+ I agree
+ </div>
+
+ <div id="aria_mixed_checkbox" role="checkbox" aria-checked="mixed">
+ I might agree
+ </div>
+
+ <div id="aria_checked_switch" role="switch" aria-checked="true">
+ I am switched on
+ </div>
+
+ <div id="aria_mixed_switch" role="switch" aria-checked="mixed">
+ I am unsupported
+ </div>
+
+ <div id="aria_modal" aria-modal="true">modal stuff</div>
+ <div id="aria_modal_false" aria-modal="false">non modal stuff</div>div>
+ <div id="aria_multiline_textbox" role="textbox" aria-multiline="true"></div>
+ <div id="aria_multiselectable_listbox" role="listbox" aria-multiselectable="true"></div>
+ <div id="aria_pressed_button" role="button" aria-pressed="true">Button</div>
+ <button id="aria_pressed_native_button" aria-pressed="true">Button</button>
+
+ <div id="aria_readonly_textbox"
+ role="textbox" aria-readonly="true">This text should be readonly</div>
+
+ <div id="aria_grid_default" role="grid">
+ <div role="row">
+ <div id="aria_grid_default_colheader_readonly"
+ role="columnheader" aria-readonly="true">colheader1</div>
+ <div id="aria_grid_default_colheader_inherited"
+ role="columnheader">colheader2</div>
+ </div>
+ <div role="row">
+ <div id="aria_grid_default_rowheader_readonly"
+ role="rowheader" aria-readonly="true">rowheader1</div>
+ <div id="aria_grid_default_rowheader_inherited"
+ role="rowheader">rowheader2</div>
+ </div>
+ <div role="row">
+ <div id="aria_grid_default_cell_readonly"
+ role="gridcell" aria-readonly="true">gridcell1</div>
+ <div id="aria_grid_default_cell_inherited"
+ role="gridcell">gridcell2</div>
+ </div>
+ </div>
+
+ <div id="aria_grid_readonly" role="grid" aria-readonly="true">
+ <div role="row">
+ <div id="aria_grid_readonly_colheader_editable"
+ role="columnheader" aria-readonly="false">colheader1</div>
+ <div id="aria_grid_readonly_colheader_inherited"
+ role="columnheader">colheader2</div>
+ </div>
+ <div role="row">
+ <div id="aria_grid_readonly_rowheader_editable"
+ role="rowheader" aria-readonly="false">rowheader1</div>
+ <div id="aria_grid_readonly_rowheader_inherited"
+ role="rowheader">rowheader2</div>
+ </div>
+ <div role="row">
+ <div id="aria_grid_readonly_cell_editable"
+ role="gridcell" aria-readonly="false">gridcell1</div>
+ <div id="aria_grid_readonly_cell_inherited"
+ role="gridcell">gridcell2</div>
+ </div>
+ </div>
+
+ <div id="aria_treegrid_default" role="grid">
+ <div role="row">
+ <div id="aria_treegrid_default_colheader_readonly"
+ role="columnheader" aria-readonly="true">colheader1</div>
+ <div id="aria_treegrid_default_colheader_inherited"
+ role="columnheader">colheader2</div>
+ </div>
+ <div role="row">
+ <div id="aria_treegrid_default_rowheader_readonly"
+ role="rowheader" aria-readonly="true">rowheader1</div>
+ <div id="aria_treegrid_default_rowheader_inherited"
+ role="rowheader">rowheader2</div>
+ </div>
+ <div role="row">
+ <div id="aria_treegrid_default_cell_readonly"
+ role="gridcell" aria-readonly="true">gridcell1</div>
+ <div id="aria_treegrid_default_cell_inherited"
+ role="gridcell">gridcell2</div>
+ </div>
+ </div>
+
+ <div id="aria_treegrid_readonly" role="grid" aria-readonly="true">
+ <div role="row">
+ <div id="aria_treegrid_readonly_colheader_editable"
+ role="columnheader" aria-readonly="false">colheader1</div>
+ <div id="aria_treegrid_readonly_colheader_inherited"
+ role="columnheader">colheader2</div>
+ </div>
+ <div role="row">
+ <div id="aria_treegrid_readonly_rowheader_editable"
+ role="rowheader" aria-readonly="false">rowheader1</div>
+ <div id="aria_treegrid_readonly_rowheader_inherited"
+ role="rowheader">rowheader2</div>
+ </div>
+ <div role="row">
+ <div id="aria_treegrid_readonly_cell_editable"
+ role="gridcell" aria-readonly="false">gridcell1</div>
+ <div id="aria_treegrid_readonly_cell_inherited"
+ role="gridcell">gridcell2</div>
+ </div>
+ </div>
+
+ <div role="listbox">
+ <div id="aria_selectable_listitem" role="option" aria-selected="true">Item1</div>
+ </div>
+
+ <!-- Test that aria-disabled state gets propagated to all descendants -->
+ <div id="group" role="group" aria-disabled="true">
+ <button>hi</button>
+ <div tabindex="0" role="listbox" aria-activedescendant="item1"
+ aria-owns="item5">
+ <div role="option" id="item1">Item 1</div>
+ <div role="option" id="item2">Item 2</div>
+ <div role="option" id="item3">Item 3</div>
+ <div role="option" id="item4">Item 4</div>
+ </div>
+ <div role="slider" tabindex="0">A slider</div>
+ </div>
+ <div role="option" id="item5">Item 5</div>
+
+ <!-- Test active state -->
+ <div id="as_listbox" tabindex="0" role="listbox"
+ aria-activedescendant="as_item1">
+ <div role="option" id="as_item1">Item 1</div>
+ <div role="option" id="as_item2">Item 2</div>
+ </div>
+
+ <!-- universal ARIA properties should be inherited by text field of file input -->
+ <input type="file" id="fileinput"
+ aria-busy="true"
+ aria-disabled="true"
+ aria-required="true"
+ aria-haspopup="true"
+ aria-invalid="true">
+
+ <div id="offscreen_log" role="log" class="offscreen">
+ <div id="aria_offscreen_textbox" role="textbox" aria-readonly="true">This text should be offscreen</div>
+ </div>
+
+ <a id="aria_menuitem_link" role="menuitem" href="foo">menuitem</a>
+ <a id="aria_button_link" role="button" href="foo">button</a>
+ <a id="aria_checkbox_link" role="checkbox" href="foo">checkbox</a>
+
+ <!-- strange edge case: please don't do this in the wild -->
+ <a id="aria_link_link" role="link" href="foo">link</a>
+ <a id="aria_link_anchor" role="link" name="link_anchor">link</a>
+
+ <!-- landmarks: links -->
+ <a id="aria_application_link" role="application" href="foo">app</a>
+ <a id="aria_main_link" role="main" href="foo">main</a>
+ <a id="aria_navigation_link" role="navigation" href="foo">nav</a>
+
+ <!-- landmarks: anchors -->
+ <a id="aria_application_anchor" role="application" name="app_anchor">app</a>
+ <a id="aria_main_anchor" role="main" name="main_anchor">main</a>
+ <a id="aria_navigation_anchor" role="navigation" name="nav_anchor">nav</a>
+
+ <!-- aria-orientation -->
+ <div id="aria_combobox" role="combobox">combobox</div>
+ <div id="aria_hcombobox" role="combobox" aria-orientation="horizontal">horizontal combobox</div>
+ <div id="aria_vcombobox" role="combobox" aria-orientation="vertical">vertical combobox</div>
+ <div id="aria_listbox" role="listbox">listbox</div>
+ <div id="aria_hlistbox" role="listbox" aria-orientation="horizontal">horizontal listbox</div>
+ <div id="aria_vlistbox" role="listbox" aria-orientation="vertical">vertical listbox</div>
+ <div id="aria_menu" role="menu">menu</div>
+ <div id="aria_hmenu" role="menu" aria-orientation="horizontal">horizontal menu</div>
+ <div id="aria_vmenu" role="menu" aria-orientation="vertical">vertical menu</div>
+ <div id="aria_menubar" role="menubar">menubar</div>
+ <div id="aria_hmenubar" role="menubar" aria-orientation="horizontal">horizontal menubar</div>
+ <div id="aria_vmenubar" role="menubar" aria-orientation="vertical">vertical menubar</div>
+ <div id="aria_radiogroup" role="radiogroup">radiogroup</div>
+ <div id="aria_hradiogroup" role="radiogroup" aria-orientation="horizontal">horizontal radiogroup</div>
+ <div id="aria_vradiogroup" role="radiogroup" aria-orientation="vertical">vertical radiogroup</div>
+ <div id="aria_scrollbar" role="scrollbar">scrollbar</div>
+ <div id="aria_hscrollbar" role="scrollbar" aria-orientation="horizontal">horizontal scrollbar</div>
+ <div id="aria_vscrollbar" role="scrollbar" aria-orientation="vertical">vertical scrollbar</div>
+ <div id="aria_separator" role="separator">separator</div>
+ <div id="aria_hseparator" role="separator" aria-orientation="horizontal">horizontal separator</div>
+ <div id="aria_vseparator" role="separator" aria-orientation="vertical">vertical separator</div>
+ <div id="aria_slider" role="slider">slider</div>
+ <div id="aria_hslider" role="slider" aria-orientation="horizontal">horizontal slider</div>
+ <div id="aria_vslider" role="slider" aria-orientation="vertical">vertical slider</div>
+
+ <div id="aria_tablist" role="tablist">tablist</div>
+ <div id="aria_htablist" role="tablist" aria-orientation="horizontal">horizontal tablist</div>
+ <div id="aria_vtablist" role="tablist" aria-orientation="vertical">vertical tablist</div>
+ <div id="aria_toolbar" role="toolbar">toolbar</div>
+ <div id="aria_htoolbar" role="toolbar" aria-orientation="horizontal">horizontal toolbar</div>
+ <div id="aria_vtoolbar" role="toolbar" aria-orientation="vertical">vertical toolbar</div>
+ <div id="aria_tree" role="tree">tree</div>
+ <div id="aria_htree" role="tree" aria-orientation="horizontal">horizontal tree</div>
+ <div id="aria_vtree" role="tree" aria-orientation="vertical">vertical tree</div>
+ <div id="aria_treegrid" role="treegrid">treegrid</div>
+ <div id="aria_htreegrid" role="treegrid" aria-orientation="horizontal">horizontal treegrid</div>
+ <div id="aria_vtreegrid" role="treegrid" aria-orientation="vertical">vertical treegrid</div>
+
+ <!-- indeterminate ARIA progressbars should expose mixed state -->
+ <div id="aria_progressbar" role="progressbar"></div>
+ <div id="aria_progressbar_valuenow" role="progressbar" aria-valuenow="1"></div>
+ <div id="aria_progressbar_valuetext" role="progressbar" aria-valuetext="value"></div>
+
+ <!-- ARIA select widget should expose focusable state regardless the way they manage its children -->
+ <div id="aria_listbox" role="listbox">
+ <div role="option" tabindex="0">A</div>
+ <div role="option" tabindex="0">a</div>
+ </div>
+ <div id="aria_grid" role="grid">
+ <div role="row"><div role="gridcell" tabindex="0">B</div></div></div>
+ <div role="row"><div role="gridcell" tabindex="0">b</div></div></div>
+ <div id="aria_tree" role="tree">
+ <div role="treeitem" tabindex="0">C</div>
+ <div role="treeitem" tabindex="0">c</div>
+ </div>
+ <div id="aria_treegrid" role="treegrid">
+ <div role="row"><div role="gridcell" tabindex="0">D</div></div>
+ <div role="row"><div role="gridcell" tabindex="0">d</div></div>
+ </div>
+ <div id="aria_listbox_disabled" role="listbox" aria-disabled="true">
+ <div role="option">E</div>
+ <div role="option">e</div>
+ </div>
+ <div id="aria_grid_disabled" role="grid" aria-disabled="true">
+ <div role="row"><div role="gridcell">F</div></div>
+ <div role="row"><div role="gridcell">f</div></div>
+ </div>
+ <div id="aria_tree_disabled" role="tree" aria-disabled="true">
+ <div role="treeitem">G</div>
+ <div role="treeitem">g</div>
+ </div>
+ <div id="aria_treegrid_disabled" role="treegrid" aria-disabled="true">
+ <div role="row"><div role="gridcell">H</div></div>
+ <div role="row"><div role="gridcell">h</div></div>
+ </div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_aria.xul b/accessible/tests/mochitest/states/test_aria.xul
new file mode 100644
index 000000000..b15cc7781
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_aria.xul
@@ -0,0 +1,60 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL ARIA state tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function doTest()
+ {
+ // aria-pressed
+ testStates("pressed_button", STATE_PRESSED, 0, STATE_CHECKABLE);
+ testStates("pressed_menu_button", STATE_PRESSED | STATE_HASPOPUP, 0, STATE_CHECKABLE);
+
+ SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1033283"
+ title="Expose pressed state on XUL menu toggle buttons">
+ Mozilla Bug 1033283
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <button id="pressed_button" aria-pressed="true" label="I am pressed" />
+ <button id="pressed_menu_button" aria-pressed="true" label="I am pressed" type="menu-button">
+ <menupopup>
+ <menuitem label="I am a menu item" />
+ </menupopup>
+ </button>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/states/test_aria_imgmap.html b/accessible/tests/mochitest/states/test_aria_imgmap.html
new file mode 100644
index 000000000..1dfe355bd
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_aria_imgmap.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test usemap elements and ARIA</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ //gA11yEventDumpToConsole = true;
+ function doPreTest()
+ {
+ waitForImageMap("imagemap", doTest);
+ }
+
+ function doTest()
+ {
+ var imageMap = getAccessible("imagemap");
+
+ var t1 = imageMap.getChildAt(0);
+ testStates(t1, 0, EXT_STATE_EDITABLE, STATE_LINKED);
+ var t2 = imageMap.getChildAt(1);
+ testStates(t2, 0, EXT_STATE_EDITABLE, STATE_LINKED);
+ var rb1 = imageMap.getChildAt(2);
+ testStates(rb1, (STATE_CHECKABLE | STATE_CHECKED), 0, STATE_LINKED);
+ var rb2 = imageMap.getChildAt(3);
+ testStates(rb2, STATE_CHECKABLE, 0, STATE_CHECKED, STATE_LINKED);
+ var cb1 = imageMap.getChildAt(4);
+ testStates(cb1, (STATE_CHECKABLE | STATE_CHECKED), 0, STATE_LINKED);
+ var cbox = imageMap.getChildAt(5);
+ testStates(cbox, (STATE_HASPOPUP | STATE_COLLAPSED),
+ EXT_STATE_EXPANDABLE, STATE_LINKED);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doPreTest);
+ </script>
+
+</head>
+<body>
+
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=548291"
+ title="ARIA states on image maps">
+Mozilla Bug 548291
+</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+
+<img id="imagemap" src="../formimage.png" width="219" height="229" border="0" usemap="#ariaMap">
+<map id="ariaMap" name="ariaMap">
+ <area id="t1" role="textbox" shape="rect" tabindex="0" alt="" title="first name" coords="4,20,108,48" href="#" />
+ <area id="t2" role="textbox" shape="rect" alt="" title="last name" coords="111,21,215,50" href="#" />
+ <area id="rb1" role="radio" aria-checked="true" shape="circle" alt="" title="male" coords="60,75,11" href="#" />
+ <area id="rb2" role="radio" shape="circle" alt="" title="female" coords="73,94,11" href="#" />
+ <area id="cb1" role="checkbox" aria-checked="true" shape="rect" alt="" title="have bike" coords="95,123,118,145" href="#" />
+ <area id="cbox" role="combobox" shape="rect" alt="" title="bike model" coords="120,124,184,146" href="#" />
+ <area id="cb2" role="checkbox" shape="rect" alt="" title="have car" coords="90,145,114,164" href="#" />
+ <area id="cb3" role="checkbox" shape="rect" alt="" title="have airplane" coords="130,163,152,184" href="#" />
+ <area id="b1" role="button" shape="rect" alt="" title="submit" coords="4,198,67,224" href="#" />
+</map>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_aria_widgetitems.html b/accessible/tests/mochitest/states/test_aria_widgetitems.html
new file mode 100644
index 000000000..81bce4194
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_aria_widgetitems.html
@@ -0,0 +1,162 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Test ARIA tab accessible selected state</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function focusARIAItem(aID, aIsSelected)
+ {
+ this.DOMNode = getNode(aID);
+
+ this.invoke = function focusARIAItem_invoke()
+ {
+ this.DOMNode.focus();
+ }
+
+ this.check = function focusARIAItem_check(aEvent)
+ {
+ testStates(this.DOMNode, aIsSelected ? STATE_SELECTED : 0, 0,
+ aIsSelected ? 0 : STATE_SELECTED);
+ }
+
+ this.getID = function focusARIAItem_getID()
+ {
+ return "Focused ARIA widget item with aria-selected='" +
+ (aIsSelected ? "true', should" : "false', shouldn't") +
+ " have selected state on " + prettyName(aID);
+ }
+ }
+
+ function focusActiveDescendantItem(aItemID, aWidgetID, aIsSelected)
+ {
+ this.DOMNode = getNode(aItemID);
+ this.widgetDOMNode = getNode(aWidgetID);
+
+ this.invoke = function focusActiveDescendantItem_invoke()
+ {
+ this.widgetDOMNode.setAttribute("aria-activedescendant", aItemID);
+ this.widgetDOMNode.focus();
+ }
+
+ this.check = function focusActiveDescendantItem_check(aEvent)
+ {
+ testStates(this.DOMNode, aIsSelected ? STATE_SELECTED : 0, 0,
+ aIsSelected ? 0 : STATE_SELECTED);
+ }
+
+ this.getID = function tabActiveDescendant_getID()
+ {
+ return "ARIA widget item managed by activedescendant " +
+ (aIsSelected ? "should" : "shouldn't") +
+ " have the selected state on " + prettyName(aItemID);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ // aria-selected
+ testStates("aria_tab1", 0, 0, STATE_SELECTED);
+ testStates("aria_tab2", STATE_SELECTED);
+ testStates("aria_tab3", 0, 0, STATE_SELECTED);
+ testStates("aria_option1", 0, 0, STATE_SELECTED);
+ testStates("aria_option2", STATE_SELECTED);
+ testStates("aria_option3", 0, 0, STATE_SELECTED);
+ testStates("aria_treeitem1", 0, 0, STATE_SELECTED);
+ testStates("aria_treeitem2", STATE_SELECTED);
+ testStates("aria_treeitem3", 0, 0, STATE_SELECTED);
+
+ // selected state when widget item is focused
+ gQueue = new eventQueue(EVENT_FOCUS);
+
+ gQueue.push(new focusARIAItem("aria_tab1", true));
+ gQueue.push(new focusARIAItem("aria_tab2", true));
+ gQueue.push(new focusARIAItem("aria_tab3", false));
+ gQueue.push(new focusARIAItem("aria_option1", true));
+ gQueue.push(new focusARIAItem("aria_option2", true));
+ gQueue.push(new focusARIAItem("aria_option3", false));
+ gQueue.push(new focusARIAItem("aria_treeitem1", true));
+ gQueue.push(new focusARIAItem("aria_treeitem2", true));
+ gQueue.push(new focusARIAItem("aria_treeitem3", false));
+
+ // selected state when widget item is focused (by aria-activedescendant)
+ gQueue.push(new focusActiveDescendantItem("aria_tab5", "aria_tablist2", true));
+ gQueue.push(new focusActiveDescendantItem("aria_tab6", "aria_tablist2", true));
+ gQueue.push(new focusActiveDescendantItem("aria_tab4", "aria_tablist2", false));
+
+ gQueue.invoke(); // SimpleTest.finish() will be called in the end
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=653601"
+ title="aria-selected ignored for ARIA tabs">
+ Mozilla Bug 653601
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=526703"
+ title="Focused widget item should expose selected state by default">
+ Mozilla Bug 526703
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- tab -->
+ <div id="aria_tablist" role="tablist">
+ <div id="aria_tab1" role="tab" tabindex="0">unselected tab</div>
+ <div id="aria_tab2" role="tab" tabindex="0" aria-selected="true">selected tab</div>
+ <div id="aria_tab3" role="tab" tabindex="0" aria-selected="false">focused explicitly unselected tab</div>
+ </div>
+
+ <!-- listbox -->
+ <div id="aria_listbox" role="listbox">
+ <div id="aria_option1" role="option" tabindex="0">unselected option</div>
+ <div id="aria_option2" role="option" tabindex="0" aria-selected="true">selected option</div>
+ <div id="aria_option3" role="option" tabindex="0" aria-selected="false">focused explicitly unselected option</div>
+ </div>
+
+ <!-- tree -->
+ <div id="aria_tree" role="tree">
+ <div id="aria_treeitem1" role="treeitem" tabindex="0">unselected treeitem</div>
+ <div id="aria_treeitem2" role="treeitem" tabindex="0" aria-selected="true">selected treeitem</div>
+ <div id="aria_treeitem3" role="treeitem" tabindex="0" aria-selected="false">focused explicitly unselected treeitem</div>
+ </div>
+
+ <!-- tab managed by active-descendant -->
+ <div id="aria_tablist2" role="tablist" tabindex="0">
+ <div id="aria_tab4" role="tab" aria-selected="false">focused explicitly unselected tab</div>
+ <div id="aria_tab5" role="tab">initially selected tab</div>
+ <div id="aria_tab6" role="tab">later selected tab</div>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_buttons.html b/accessible/tests/mochitest/states/test_buttons.html
new file mode 100644
index 000000000..52c642ce5
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_buttons.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML button accessible states</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ // Default state.
+ testStates("f1_image", STATE_DEFAULT | STATE_FOCUSABLE);
+ testStates("f2_submit", STATE_DEFAULT | STATE_FOCUSABLE);
+ testStates("f3_submitbutton", STATE_DEFAULT | STATE_FOCUSABLE);
+ testStates("f3_disabled_reset", STATE_UNAVAILABLE, 0, STATE_FOCUSABLE, 0);
+ testStates("f4_button", STATE_FOCUSABLE, 0, STATE_DEFAULT);
+ testStates("f4_disabled_button", STATE_UNAVAILABLE, 0, STATE_FOCUSABLE, 0);
+ testStates("f4_image1", STATE_DEFAULT | STATE_FOCUSABLE);
+ testStates("f4_image2", STATE_FOCUSABLE, 0, STATE_DEFAULT);
+ testStates("f4_submit", STATE_FOCUSABLE, 0, STATE_DEFAULT);
+ testStates("f4_submitbutton", STATE_FOCUSABLE, 0, STATE_DEFAULT);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=664142"
+ title="DEFAULT state exposed incorrectly for HTML">
+ Mozilla Bug 664142
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <p>A form with an image button</p>
+ <form name="form1" method="get">
+ <input type="text" name="hi">
+
+ <input id="f1_image" type="image" value="image-button">
+ </form>
+
+ <p>A form with a submit button:</p>
+ <form name="form2" method="get">
+ <input type="text" name="hi">
+ <input id="f2_submit" type="submit">
+ </form>
+
+ <p>A form with a HTML4 submit button:</p>
+ <form name="form3" method="get">
+ <input type="text" name="hi">
+ <button id="f3_submitbutton" type="submit">submit</button>
+ <button id="f3_disabled_reset" type="reset" disabled>reset</button>
+ </form>
+
+ <p>A form with normal button, two image buttons, submit button,
+ HTML4 submit button:</p>
+ <form name="form4" method="get">
+ <input type="text" name="hi">
+ <input id="f4_button" type="button" value="normal" name="normal-button">
+ <input id="f4_disabled_button" type="button" value="disabled" name="disabled-button" disabled>
+ <input id="f4_image1" type="image" value="image-button1" name="image-button1">
+ <input id="f4_image2" type="image" value="image-button2" name="image-button2">
+ <input id="f4_submit" type="submit" value="real-submit" name="real-submit">
+ <button id="f4_submitbutton" type="submit">submit</button>
+ </form>
+
+ </body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_controls.html b/accessible/tests/mochitest/states/test_controls.html
new file mode 100644
index 000000000..7369f6d01
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_controls.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML control states</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ // Undetermined progressbar (no value or aria-value attribute): mixed state
+ testStates("progress", STATE_MIXED);
+ // Determined progressbar (has value): shouldn't have mixed state
+ testStates("progress2", 0, 0, STATE_MIXED);
+ // Determined progressbar (has aria-value): shouldn't have mixed state
+ // testStates("progress3", 0, 0, STATE_MIXED);
+ todo(false, "we should respect ARIA");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=670853"
+ title="Bug 670853 - undetermined progressmeters should expose mixed state">
+ Mozilla Bug 670853
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <progress id="progress"></progress>
+ <progress id="progress2" value="1"></progress>
+ <progress id="progress3" aria-valuenow="1"></progress>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_controls.xul b/accessible/tests/mochitest/states/test_controls.xul
new file mode 100644
index 000000000..83de45256
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_controls.xul
@@ -0,0 +1,182 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL input control state tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function openColorpicker(aID)
+ {
+ this.popupNode = getNode(aID).mPicker.parentNode;
+ this.popup = getAccessible(this.popupNode);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.popupNode)
+ ];
+
+ this.invoke = function openColorpicker_invoke()
+ {
+ getNode(aID).showPopup();
+ }
+
+ this.finalCheck = function openColorpicker_finalCheck()
+ {
+ testStates(this.popup.firstChild,
+ STATE_FOCUSABLE | STATE_SELECTABLE, 0,
+ STATE_UNAVAILABLE);
+ }
+
+ this.getID = function openColorpicker_getID()
+ {
+ return "open colorpicker";
+ }
+ }
+
+ var gQueue = null;
+ function doTest()
+ {
+ testStates("checkbox", STATE_FOCUSABLE, 0, STATE_UNAVAILABLE);
+ testStates("checkbox2", STATE_UNAVAILABLE, 0 , STATE_FOCUSABLE);
+ testStates("radiogroup", 0, 0, STATE_FOCUSABLE | STATE_UNAVAILABLE);
+ testStates("radio", STATE_FOCUSABLE, 0, STATE_UNAVAILABLE);
+ testStates("radio-disabled", STATE_UNAVAILABLE, 0 , STATE_FOCUSABLE);
+ testStates("radiogroup-disabled", STATE_UNAVAILABLE, 0 , STATE_FOCUSABLE);
+ testStates("radio-disabledradiogroup", STATE_UNAVAILABLE, 0 , STATE_FOCUSABLE);
+ testStates("button", STATE_FOCUSABLE, 0, STATE_UNAVAILABLE);
+ testStates("button-disabled", STATE_UNAVAILABLE, 0 , STATE_FOCUSABLE);
+ testStates("colorpicker", STATE_FOCUSABLE | STATE_HASPOPUP, 0, STATE_UNAVAILABLE);
+ testStates("colorpicker-disabled", STATE_HASPOPUP, 0, STATE_FOCUSABLE);
+ testStates("combobox", STATE_FOCUSABLE | STATE_HASPOPUP, 0, STATE_UNAVAILABLE);
+ testStates("combobox-disabled", STATE_UNAVAILABLE | STATE_HASPOPUP, 0, STATE_FOCUSABLE);
+ testStates("listbox", STATE_FOCUSABLE, 0, STATE_UNAVAILABLE);
+ testStates("listitem", STATE_FOCUSABLE | STATE_SELECTABLE, 0, STATE_UNAVAILABLE);
+ testStates("listbox-disabled", STATE_UNAVAILABLE, 0, STATE_FOCUSABLE | STATE_SELECTABLE);
+ testStates("listitem-disabledlistbox", STATE_UNAVAILABLE, 0, STATE_FOCUSABLE | STATE_SELECTABLE);
+ testStates("menubar", 0, 0, STATE_FOCUSABLE);
+ testStates("menu", STATE_FOCUSABLE, 0, STATE_UNAVAILABLE);
+ testStates("menu-disabled", STATE_UNAVAILABLE, 0, STATE_FOCUSABLE | STATE_SELECTABLE);
+ testStates("scale", STATE_FOCUSABLE, 0, STATE_UNAVAILABLE);
+ testStates("scale-disabled", STATE_UNAVAILABLE, 0, STATE_FOCUSABLE);
+ testStates("tab", STATE_FOCUSABLE | STATE_SELECTABLE | STATE_SELECTED, 0, STATE_UNAVAILABLE);
+ testStates("tab-disabled", STATE_UNAVAILABLE, 0, STATE_FOCUSABLE | STATE_SELECTABLE | STATE_SELECTED);
+
+ gQueue = new eventQueue();
+ gQueue.push(new openColorpicker("colorpicker"));
+ gQueue.invoke(); // Will call SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=599163"
+ title="check disabled state instead of attribute">
+ Mozilla Bug 599163
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=756983"
+ title="Isolate focusable and unavailable states from State()">
+ Mozilla Bug 756983
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+
+ <checkbox id="checkbox" checked="true" label="Steak"/>
+ <checkbox id="checkbox2" checked="true" label="Salad" disabled="true"/>
+
+ <radiogroup id="radiogroup">
+ <radio id="radio" label="Orange"/>
+ <radio id="radio-disabled" selected="true" label="Violet" disabled="true"/>
+ </radiogroup>
+
+ <radiogroup id="radiogroup-disabled" disabled="true">
+ <radio id="radio-disabledradiogroup" label="Orange"/>
+ <radio id="violet2" selected="true" label="Violet"/>
+ </radiogroup>
+
+ <button id="button" value="button"/>
+ <button id="button-disabled" disabled="true" value="button"/>
+
+ <colorpicker id="colorpicker" type="button"/>
+ <colorpicker id="colorpicker-disabled" type="button" disabled="true"/>
+
+ <menulist id="combobox">
+ <menupopup>
+ <menuitem label="item1"/>
+ </menupopup>
+ </menulist>
+
+ <menulist id="combobox-disabled" disabled="true">
+ <menupopup>
+ <menuitem label="item1"/>
+ </menupopup>
+ </menulist>
+
+ <listbox id="listbox">
+ <listitem id="listitem" label="list item"/>
+ </listbox>
+
+ <listbox id="listbox-disabled" disabled="true">
+ <listitem id="listitem-disabledlistbox" label="list item"/>
+ </listbox>
+
+ <toolbox>
+ <menubar id="menubar">
+ <menu id="menu" label="menu1">
+ <menupopup>
+ <menuitem id="menu1-item1" label="menuitem1.1"/>
+ </menupopup>
+ </menu>
+ <menu id="menu-disabled" label="menu2" disabled="true">
+ <menupopup>
+ <menuitem id="menu-disabled-item1" label="menuitem2.1"/>
+ </menupopup>
+ </menu>
+ </menubar>
+ </toolbox>
+
+ <scale id="scale" min="1" max="10"/>
+ <scale id="scale-disabled" min="1" max="10" disabled="true"/>
+
+ <tabbox>
+ <tabs>
+ <tab id="tab" label="tab1" tooltip="tooltip"/>
+ <tab id="tab-disabled" label="tab1" disabled="true"/>
+ </tabs>
+ <tabpanels>
+ <tabpanel/>
+ <tabpanel/>
+ </tabpanels>
+ </tabbox>
+
+ <tooltip id="tooltip"><description>tooltip</description></tooltip>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/states/test_doc.html b/accessible/tests/mochitest/states/test_doc.html
new file mode 100644
index 000000000..83edd9dac
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_doc.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>states of document</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ // Bug 566542: root accesible should expose active state when focused.
+ testStates(getRootAccessible(), 0, EXT_STATE_ACTIVE);
+
+ // Bug 509696, 607219.
+ testStates(document, STATE_READONLY, 0); // role=""
+
+ document.body.setAttribute("role","banner"); // no platform mapping
+ testStates(document, STATE_READONLY);
+ document.body.setAttribute("role","foo"); // bogus role
+ testStates(document, STATE_READONLY);
+ document.body.removeAttribute("role");
+ testStates(document, STATE_READONLY);
+
+ // Bugs 454997 and 467387
+ testStates(document, STATE_READONLY);
+ testStates("document", STATE_READONLY);
+ testStates("editable_document", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+
+ document.designMode = "on";
+
+ testStates(document, 0, EXT_STATE_EDITABLE, STATE_READONLY);
+ testStates("p", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+ testStates("document", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+ testStates("editable_document", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+
+ document.designMode = "off";
+
+ testStates(document, STATE_READONLY);
+ testStates("document", STATE_READONLY);
+ testStates("editable_document", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body role="">
+
+ <a target="_blank"
+ title="<body contenteditable='true'> exposed incorrectly"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=454997">Mozilla Bug 454997</a>
+ <a target="_blank"
+ title="nsIAccessible states tests of editable document"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=467387">Mozilla Bug 467387</a>
+ <a target="_blank"
+ title="Role attribute on body with empty string causes DocAccessible not to have read-only state."
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=509696">Mozilla Bug 509696</a>
+ <a target="_blank"
+ title="Frame for firefox does not implement the state "active" when firefox is the active frame"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=566542">Mozilla Bug 566542</a>
+ <a target="_blank"
+ title="Dynamic role attribute change on body doesn't affect on document role"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=607219">Mozilla Bug 607219</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <p id="p">hello</p>
+
+ <div id="document" role="document">document</div>
+ <div id="editable_document" role="document" contentEditable="true">editable document</doc>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_doc_busy.html b/accessible/tests/mochitest/states/test_doc_busy.html
new file mode 100644
index 000000000..8e1422da1
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_doc_busy.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>states of document</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ //gA11yEventDumpToConsole = true; // debugging stuff
+
+ function loadFile()
+ {
+ var eventSeq = [
+ new stateChangeChecker(STATE_BUSY, false, true, document, null, false, true),
+ new stateChangeChecker(STATE_BUSY, false, false, document)
+ ];
+ defineScenario(this, eventSeq); // both events were fired
+
+ var unexpectedEventSeq = [
+ new stateChangeChecker(STATE_BUSY, false, true, document),
+ new stateChangeChecker(STATE_BUSY, false, false, document)
+ ];
+ defineScenario(this, [], unexpectedEventSeq); // events were coalesced
+
+ this.invoke = function loadFile_invoke()
+ {
+ synthesizeMouse(getNode("link"), 1, 1, {});
+ }
+
+ this.getID = function loadFile_getID()
+ {
+ return "load file: state busy change events on document";
+ }
+ }
+
+ var gQueue = null;
+ function doTest()
+ {
+ // State busy change event on file loading.
+ //enableLogging("docload"); // debugging
+ gQueue = new eventQueue();
+ gQueue.push(new loadFile());
+ //gQueue.onFinish = function() { disableLogging(); } // debugging
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ title="Missing busy state change event when downloading files"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=446469">Bug 446469</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <a id="link" href="http://example.com/a11y/accessible/tests/mochitest/dumbfile.zip">a file</a>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_docarticle.html b/accessible/tests/mochitest/states/test_docarticle.html
new file mode 100644
index 000000000..1ac62ee4b
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_docarticle.html
@@ -0,0 +1,80 @@
+<html>
+<head>
+ <title>states of document article</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ var docAcc = getAccessible(document, [nsIAccessibleDocument]);
+ if (docAcc) {
+ testStates(docAcc, STATE_READONLY);
+ testStates("aria_article", STATE_READONLY);
+ testStates("editable_aria_article", 0, EXT_STATE_EDITABLE,
+ STATE_READONLY);
+ testStates("article", STATE_READONLY);
+ testStates("editable_article", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+
+ document.designMode = "on";
+
+ testStates(docAcc, 0, EXT_STATE_EDITABLE, STATE_READONLY);
+ testStates("aria_article", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+ testStates("editable_aria_article", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+ testStates("article", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+ testStates("editable_article", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+
+ document.designMode = "off";
+
+ testStates(docAcc, STATE_READONLY);
+ testStates("aria_article", STATE_READONLY);
+ testStates("editable_aria_article", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+ testStates("article", STATE_READONLY);
+ testStates("editable_article", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+ }
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body role="article">
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=467387"
+ title="Expose non-editable documents as readonly, regardless of role">
+ Mozilla Bug 467387
+ </a><br/>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=613502"
+ title="Map <article> like we do aria role article">
+ Mozilla Bug 613502
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="aria_article" role="article">aria article</div>
+ <div id="editable_aria_article" role="article" contentEditable="true">
+ editable aria article</div>
+
+ <article id="article">article</article>
+ <article id="editable_article" contentEditable="true">
+ editable article</article>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_editablebody.html b/accessible/tests/mochitest/states/test_editablebody.html
new file mode 100644
index 000000000..3b1486876
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_editablebody.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=454997
+-->
+<head>
+ <title>nsIAccessible states tests of contenteditable body</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ testStates(document, 0, EXT_STATE_EDITABLE);
+ testStates("p", 0, EXT_STATE_EDITABLE);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body contentEditable="true">
+
+ <a target="_blank"
+ title="nsIAccessible states tests of contenteditable body"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=454997">Mozilla Bug 454997</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <p id="p">hello</p>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_expandable.xul b/accessible/tests/mochitest/states/test_expandable.xul
new file mode 100644
index 000000000..1e210a0ef
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_expandable.xul
@@ -0,0 +1,118 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<!-- Firefox searchbar -->
+<?xml-stylesheet href="chrome://browser/content/browser.css"
+ type="text/css"?>
+<!-- SeaMonkey searchbar -->
+<?xml-stylesheet href="chrome://navigator/content/navigator.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Expanded state change events tests for comboboxes and autocompletes.">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
+
+ <script type="application/javascript"
+ src="../autocomplete.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ //gA11yEventDumpToConsole = true; // debuggin
+
+ var gQueue = null;
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new openCombobox("menulist"));
+ gQueue.push(new closeCombobox("menulist"));
+
+ todo(false, "Autocompletes don't fire expanded state change events when popup open. See bug 688480!");
+ //gQueue.push(new openCombobox("autocomplete"));
+ //gQueue.push(new closeCombobox("autocomplete"));
+
+ // XXX: searchbar doesn't fire state change events because accessible
+ // parent of combobox_list accessible is pushbutton accessible.
+ //var searchbar = document.getElementById("searchbar");
+ //gQueue.push(new openHideCombobox(searchbar, true));
+ //gQueue.push(new openHideCombobox(searchbar, false));
+ todo(false, "Enable states test for XUL searchbar widget!");
+
+ gQueue.onFinish = function()
+ {
+ // unregister 'test-a11y-search' autocomplete search
+ shutdownAutoComplete();
+ }
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ // This is the hacks needed to use a searchbar without browser.js.
+ function getBrowser()
+ {
+ return {
+ mCurrentBrowser: { engines: new Array() }
+ };
+ }
+ var BrowserSearch = {
+ updateOpenSearchBadge: function() {}
+ };
+
+ SimpleTest.waitForExplicitFinish();
+
+ // Register 'test-a11y-search' autocomplete search.
+ // XPFE AutoComplete needs to register early.
+ initAutoComplete([ "hello", "hi" ],
+ [ "Beep beep'm beep beep yeah", "Baby you can drive my car" ]);
+
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox style="overflow: auto;" flex="1">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=467057"
+ title="xul menulist doesn't fire expand/collapse state change events">
+ Mozilla Bug 467057
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <menulist id="menulist">
+ <menupopup>
+ <menuitem label="item1"/>
+ <menuitem label="item2"/>
+ <menuitem label="item3"/>
+ </menupopup>
+ </menulist>
+
+ <textbox id="autocomplete" type="autocomplete"
+ autocompletesearch="test-a11y-search"/>
+
+ <searchbar id="searchbar"/>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/states/test_frames.html b/accessible/tests/mochitest/states/test_frames.html
new file mode 100644
index 000000000..cb3bca844
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_frames.html
@@ -0,0 +1,95 @@
+<html>
+
+<head>
+ <title>frame based document testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ if (navigator.platform.startsWith("Win")) {
+ SimpleTest.expectAssertions(0, 2);
+ }
+
+ function doTest()
+ {
+ frameDoc = document.getElementById("frame_doc").contentDocument;
+ frameDocArticle = document.getElementById("frame_doc_article").contentDocument;
+ frameDocCheckbox = document.getElementById("frame_doc_checkbox").contentDocument;
+ frameDocTextbox = document.getElementById("frame_doc_textbox").contentDocument;
+
+ testStates(frameDoc, STATE_READONLY, 0, 0, 0,
+ "test1: frameDoc");
+ testStates(frameDocArticle, STATE_READONLY, 0, 0, 0,
+ "test1: frameDocArticle");
+ testStates(frameDocCheckbox, 0, 0, STATE_READONLY, 0,
+ "test1: frameDocCheckbox");
+ testStates(frameDocTextbox, 0, EXT_STATE_EDITABLE, STATE_READONLY, 0,
+ "test1: frameDocTextbox");
+ frameDoc.designMode = "on";
+ testStates(frameDoc, 0, EXT_STATE_EDITABLE, STATE_READONLY, 0,
+ "test2: frameDoc");
+ testStates(frameDocArticle, STATE_READONLY, 0, 0, 0,
+ "test2: frameDocArticle");
+ testStates(frameDocCheckbox, 0, 0, STATE_READONLY, 0,
+ "test2: frameDocCheckbox");
+ testStates(frameDocTextbox, 0, EXT_STATE_EDITABLE, STATE_READONLY, 0,
+ "test2: frameDocTextbox");
+
+ frameDocArticle.designMode = "on";
+ testStates(frameDocArticle, 0, EXT_STATE_EDITABLE, STATE_READONLY, 0,
+ "test3: frameDocArticle");
+
+ frameDocCheckbox.designMode = "on";
+ testStates(frameDocCheckbox, 0, 0, STATE_READONLY, 0,
+ "test4: frameDocCheckbox");
+
+ // Replace iframe document body before the document accessible tree is
+ // created. Check the states are updated for new body.
+ var frameUpdateDoc =
+ document.getElementById("frame_updatedoc").contentDocument;
+ testStates(frameUpdateDoc, 0, EXT_STATE_EDITABLE,
+ STATE_READONLY, EXT_STATE_STALE, "test5: frameUpdateDoc");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=467387"
+ title="Expose non-editable documents as readonly, regardless of role">
+ Mozilla Bug 467387
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=638106"
+ title="CKEditor document should be editable">
+ Mozilla Bug 638106
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <iframe id="frame_doc" src="z_frames.html"></iframe>
+ <iframe id="frame_doc_article" src="z_frames_article.html"></iframe>
+ <iframe id="frame_doc_checkbox" src="z_frames_checkbox.html"></iframe>
+ <iframe id="frame_doc_textbox" src="z_frames_textbox.html"></iframe>
+ <iframe id="frame_updatedoc" src="z_frames_update.html"></iframe>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_inputs.html b/accessible/tests/mochitest/states/test_inputs.html
new file mode 100644
index 000000000..20c6deaf8
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_inputs.html
@@ -0,0 +1,271 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML input states</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ ////////////////////////////////////////////////////////////////////////////
+ // 'editable' and 'multiline' states.
+ testStates("input", 0, EXT_STATE_EDITABLE, 0, EXT_STATE_MULTI_LINE);
+ testStates("textarea", 0, EXT_STATE_EDITABLE | EXT_STATE_MULTI_LINE);
+
+ testStates("input_readonly", 0, EXT_STATE_EDITABLE);
+ testStates("input_disabled", 0, EXT_STATE_EDITABLE);
+ testStates("textarea_readonly", 0, EXT_STATE_EDITABLE);
+ testStates("textarea_disabled", 0, EXT_STATE_EDITABLE);
+
+ ////////////////////////////////////////////////////////////////////////////
+ // 'required', 'readonly' and 'unavailable' states.
+ var maybe_required = ["input","search","radio","checkbox","textarea"];
+ var never_required = ["submit","button","reset","image"];
+
+ var i;
+ for (i in maybe_required) {
+ testStates(maybe_required[i],
+ STATE_FOCUSABLE, 0,
+ STATE_REQUIRED | STATE_READONLY | STATE_UNAVAILABLE);
+
+ testStates(maybe_required[i] + "_required",
+ STATE_FOCUSABLE | STATE_REQUIRED, 0,
+ STATE_UNAVAILABLE | STATE_READONLY);
+
+ var readonlyID = maybe_required[i] + "_readonly";
+ if (document.getElementById(readonlyID)) {
+ testStates(readonlyID,
+ STATE_FOCUSABLE | STATE_READONLY, 0,
+ STATE_UNAVAILABLE | STATE_REQUIRED);
+ }
+
+ testStates(maybe_required[i] + "_disabled",
+ STATE_UNAVAILABLE, 0,
+ STATE_FOCUSABLE | STATE_READONLY | STATE_REQUIRED);
+ }
+
+ for (i in never_required) {
+ testStates(never_required[i], 0, 0, STATE_REQUIRED | EXT_STATE_EDITABLE);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // inherited 'unavailable' state
+ testStates("f", STATE_UNAVAILABLE);
+ testStates("f_input", STATE_UNAVAILABLE);
+ testStates("f_input_disabled", STATE_UNAVAILABLE);
+
+ ////////////////////////////////////////////////////////////////////////////
+ // inherited from file control
+ var fileBrowseButton = getAccessible("file").firstChild;
+ testStates(fileBrowseButton, STATE_UNAVAILABLE | STATE_REQUIRED);
+ // No states on the label.
+
+ ////////////////////////////////////////////////////////////////////////////
+ // 'invalid' state
+ var invalid = ["pattern","email","url"];
+ for (i in invalid) {
+ testStates(invalid[i], STATE_INVALID);
+ testStates(invalid[i] + "2", 0, 0, STATE_INVALID);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // not 'invalid' state
+ // (per spec, min/maxlength are always valid until interactively edited)
+ var validInput = document.createElement("input");
+ validInput.maxLength = '0';
+ validInput.value = 'a';
+ ok(validInput.validity.valid,
+ "input should be valid despite maxlength (no interactive edits)");
+
+ var validInput2 = document.createElement("input");
+ validInput2.minLength = '1';
+ validInput2.value = '';
+ ok(validInput2.validity.valid,
+ "input should be valid despite minlength (no interactive edits)");
+
+ var valid = ["minlength","maxlength"];
+ for (i in valid) {
+ testStates(valid[i], 0, 0, STATE_INVALID);
+ testStates(valid[i] + "2", 0, 0, STATE_INVALID);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // 'invalid' state
+ // (per spec, min/maxlength validity is affected by interactive edits)
+ var mininp = document.getElementById("minlength");
+ mininp.focus();
+ mininp.setSelectionRange(mininp.value.length, mininp.value.length);
+ synthesizeKey("VK_BACK_SPACE", {});
+ ok(!mininp.validity.valid,
+ "input should be invalid after interactive edits");
+ testStates(mininp, STATE_INVALID);
+ // inputs currently cannot be made longer than maxlength interactively,
+ // so we're not testing that case.
+
+ ////////////////////////////////////////////////////////////////////////////
+ // autocomplete states
+ testStates("autocomplete-default", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("autocomplete-off", 0, 0, 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("autocomplete-formoff", 0, 0, 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("autocomplete-list", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("autocomplete-list2", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("autocomplete-tel", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("autocomplete-email", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+ testStates("autocomplete-search", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
+
+ ////////////////////////////////////////////////////////////////////////////
+ // haspopup
+ testStates("autocomplete-list", STATE_HASPOPUP);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=559275"
+ title="map attribute required to STATE_REQUIRED">
+ Bug 559275
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=389238"
+ title="Support disabled state on fieldset">
+ Bug 389238
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=599163"
+ title="check disabled state instead of attribute">
+ Bug 599163
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=601205"
+ title="Expose intrinsic invalid state to accessibility API">
+ Bug 601205
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=601205"
+ title="Expose intrinsic invalid state to accessibility API">
+ Bug 601205
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=559766"
+ title="Add accessibility support for @list on HTML input and for HTML datalist">
+ Bug 559766
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=699017"
+ title="File input control should be propogate states to descendants">
+ Bug 699017
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=733382"
+ title="Editable state bit should be present on readonly inputs">
+ Bug 733382
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=878590"
+ title="HTML5 datalist is not conveyed by haspopup property">
+ Bug 878590
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+
+ <form>
+ <input id="input" type="input">
+ <input id="input_required" type="input" required>
+ <input id="input_readonly" type="input" readonly>
+ <input id="input_disabled" type="input" disabled>
+ <input id="search" type="search">
+ <input id="search_required" type="search" required>
+ <input id="search_readonly" type="search" readonly>
+ <input id="search_disabled" type="search" disabled>
+ <input id="radio" type="radio">
+ <input id="radio_required" type="radio" required>
+ <input id="radio_disabled" type="radio" disabled>
+ <input id="checkbox" type="checkbox">
+ <input id="checkbox_required" type="checkbox" required>
+ <input id="checkbox_disabled" type="checkbox" disabled>
+ <textarea id="textarea"></textarea>
+ <textarea id="textarea_required" required></textarea>
+ <textarea id="textarea_readonly" readonly></textarea>
+ <textarea id="textarea_disabled" disabled></textarea>
+ </form>
+
+ <!-- bogus required usage -->
+ <input id="submit" type="submit" required>
+ <input id="button" type="button" required>
+ <input id="reset" type="reset" required>
+ <input id="image" type="image" required>
+
+ <!-- inherited disabled -->
+ <fieldset id="f" disabled>
+ <input id="f_input">
+ <input id="f_input_disabled" disabled>
+ </fieldset>
+
+ <!-- inherited from input@type="file" -->
+ <input id="file" type="file" required disabled>
+
+ <!-- invalid/valid -->
+ <input id="maxlength" maxlength="1" value="f">
+ <input id="maxlength2" maxlength="100" value="foo">
+ <input id="minlength" minlength="2" value="fo">
+ <input id="minlength2" minlength="1" value="foo">
+ <input id="pattern" pattern="bar" value="foo">
+ <input id="pattern2" pattern="bar" value="bar">
+ <input id="email" type="email" value="foo">
+ <input id="email2" type="email" value="foo@bar.com">
+ <input id="url" type="url" value="foo">
+ <input id="url2" type="url" value="http://mozilla.org/">
+
+ <!-- autocomplete -->
+ <input id="autocomplete-default">
+ <input id="autocomplete-off" autocomplete="off">
+ <form autocomplete="off">
+ <input id="autocomplete-formoff">
+ </form>
+ <datalist id="cities">
+ <option>Paris</option>
+ <option>San Francisco</option>
+ </datalist>
+ <input id="autocomplete-list" list="cities">
+ <input id="autocomplete-list2" list="cities" autocomplete="off">
+ <input id="autocomplete-tel" type="tel">
+
+ Email Address:
+ <input id="autocomplete-email" type="email" list="contacts" value="xyzzy">
+ <datalist id="contacts">
+ <option>xyzzy@plughs.com</option>
+ <option>nobody@mozilla.org</option>
+ </datalist>
+
+ </br>Search for:
+ <input id="autocomplete-search" type="search" list="searchhisty" value="Gamma">
+ <datalist id="searchhisty">
+ <option>Gamma Rays</option>
+ <option>Gamma Ray Bursts</option>
+ </datalist>
+
+ </body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_link.html b/accessible/tests/mochitest/states/test_link.html
new file mode 100644
index 000000000..3f89f9f7f
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_link.html
@@ -0,0 +1,144 @@
+<html>
+
+<head>
+ <title>HTML link states testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var gLinkWindow = null;
+ function closeDocChecker()
+ {
+ this.__proto__ = new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE);
+
+ this.check = function closeDocChecker_check(aEvent)
+ {
+ gLinkWindow = aEvent.accessible.rootDocument.window;
+ }
+
+ this.match = function closeDocChecker_match(aEvent)
+ {
+ // A temporary about:blank document gets loaded before 'example.com'
+ // document.
+ return aEvent.DOMNode.URL == "http://www.example.com/";
+ }
+ }
+
+ function clickLink(aID)
+ {
+ this.eventSeq = [
+ new stateChangeChecker(STATE_TRAVERSED, false, true, "link_traversed"),
+ new closeDocChecker()
+ ];
+
+ this.invoke = function clickLink_invoke()
+ {
+ synthesizeMouse(getNode("link_traversed"), 1, 1, { shiftKey: true });
+ }
+
+ this.getID = function clickLink_getID()
+ {
+ return "link + '" + aID + "' clicked.";
+ }
+ }
+
+ var gQueue = null;
+ function doTest()
+ {
+ // a@href and its text node
+ testStates("link_href", STATE_LINKED);
+ testStates(getAccessible("link_href").firstChild, STATE_LINKED);
+
+ // a@onclick
+ testStates("link_click", STATE_LINKED);
+
+ // a@onmousedown
+ testStates("link_mousedown", STATE_LINKED);
+
+ // a@onmouseup
+ testStates("link_mouseup", STATE_LINKED);
+
+ // a@role="link"
+ testStates("link_arialink", STATE_LINKED);
+
+ // a@role="button"
+ testStates("link_ariabutton", 0, 0, STATE_LINKED);
+
+ // a (no @href, no click event listener)
+ testStates("link_notlink", 0, 0, STATE_LINKED);
+
+ // a: no traversed state
+ testStates("link_traversed", 0, 0, STATE_TRAVERSED);
+
+ // a: traversed state
+ //enableLogging("docload"); // debug stuff
+
+ gQueue = new eventQueue();
+ gQueue.push(new clickLink("link_traversed"));
+ gQueue.onFinish =
+ function()
+ {
+ gLinkWindow.close();
+ //disableLogging(); // debug stuff
+ }
+
+ gQueue.invoke(); // will call SimpleTest.finsih();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=423409"
+ title="Expose click action if mouseup and mousedown are registered">
+ Mozilla Bug 423409
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=754830"
+ title="Calculate link states separately">
+ Mozilla Bug 754830
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=757774"
+ title="Fire state change event when link is traversed">
+ Mozilla Bug 757774
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <a id="link_href" href="http://mozilla.org">link</a>
+ <a id="link_click" onclick="">link</a>
+ <a id="link_mousedown" onmousedown="">link</a>
+ <a id="link_mouseup" onmouseup="">link</a>
+ <a id="link_arialink" role="link">aria link</a>
+ <a id="link_ariabutton" role="button">aria button</a>
+ <a id="link_notlink">not link</a>
+
+ <a id="link_traversed" href="http://www.example.com" target="_top">example.com</a>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_popup.xul b/accessible/tests/mochitest/states/test_popup.xul
new file mode 100644
index 000000000..bc40a8b70
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_popup.xul
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL popup attribute test">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function doTest()
+ {
+ // label with popup
+ testStates("labelWithPopup", STATE_HASPOPUP);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=504252"
+ title="Expose STATE_HASPOPUP on XUL elements that have an @popup attribute">
+ Mozilla Bug 504252
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <!-- label with popup attribute -->
+ <label id="labelWithPopup" value="file name"
+ popup="fileContext"
+ tabindex="0"/>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/states/test_selects.html b/accessible/tests/mochitest/states/test_selects.html
new file mode 100644
index 000000000..0029fcbe4
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_selects.html
@@ -0,0 +1,203 @@
+<html>
+
+<head>
+ <title>HTML selects accessible states tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function openComboboxNCheckStates(aID)
+ {
+ this.combobox = getAccessible(aID);
+ this.comboboxList = this.combobox.firstChild;
+ this.comboboxOption = this.comboboxList.firstChild;
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, this.comboboxOption)
+ ];
+
+ this.invoke = function openComboboxNCheckStates_invoke()
+ {
+ getNode(aID).focus();
+ synthesizeKey("VK_DOWN", { altKey: true });
+ }
+
+ this.finalCheck = function openComboboxNCheckStates_invoke()
+ {
+ // Expanded state on combobox.
+ testStates(this.combobox, STATE_EXPANDED);
+
+ // Floating state on combobox list.
+ testStates(this.comboboxList, STATE_FLOATING);
+ }
+
+ this.getID = function openComboboxNCheckStates_getID()
+ {
+ return "open combobox and test states";
+ }
+ }
+
+ //gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+ function doTest()
+ {
+ // combobox
+ var combobox = getAccessible("combobox");
+ testStates(combobox,
+ STATE_HASPOPUP | STATE_COLLAPSED | STATE_FOCUSABLE, 0,
+ STATE_FOCUSED, 0);
+
+ var comboboxList = combobox.firstChild;
+ testStates(comboboxList, STATE_INVISIBLE, 0, STATE_FOCUSABLE, 0);
+
+ var opt1 = comboboxList.firstChild;
+ testStates(opt1, STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE,
+ EXT_STATE_ACTIVE, STATE_FOCUSED, 0);
+
+ var opt2 = comboboxList.lastChild;
+ testStates(opt2, STATE_SELECTABLE | STATE_FOCUSABLE, 0, STATE_SELECTED, 0,
+ STATE_FOCUSED, EXT_STATE_ACTIVE);
+
+ // collapsed combobox
+ testStates("collapsedcombobox",
+ STATE_COLLAPSED | STATE_FOCUSABLE, 0,
+ STATE_FOCUSED, 0);
+
+ testStates("collapsed-1",
+ STATE_FOCUSABLE | STATE_SELECTABLE, 0,
+ STATE_OFFSCREEN | STATE_INVISIBLE, 0);
+
+ testStates("collapsed-2",
+ STATE_OFFSCREEN, 0,
+ STATE_INVISIBLE, 0);
+
+ // listbox
+ testStates("listbox",
+ STATE_FOCUSABLE, 0,
+ STATE_HASPOPUP | STATE_COLLAPSED | STATE_FOCUSED);
+
+ testStates("listitem-active",
+ STATE_FOCUSABLE | STATE_SELECTABLE, EXT_STATE_ACTIVE,
+ STATE_SELECTED | STATE_FOCUSED);
+
+ testStates("listitem",
+ STATE_FOCUSABLE | STATE_SELECTABLE, 0,
+ STATE_SELECTED | STATE_FOCUSED, EXT_STATE_ACTIVE);
+
+ testStates("listitem-disabled",
+ STATE_UNAVAILABLE, 0,
+ STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE,
+ EXT_STATE_ACTIVE);
+
+ testStates("listgroup",
+ 0, 0,
+ STATE_UNAVAILABLE | STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE,
+ EXT_STATE_ACTIVE);
+
+ testStates("listgroup-disabled",
+ STATE_UNAVAILABLE, 0,
+ STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE,
+ EXT_STATE_ACTIVE);
+
+ todo(false, "no unavailable state on option in disabled group (bug 759666)");
+// testStates("listitem-disabledgroup",
+// STATE_UNAVAILABLE, 0,
+// STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE,
+// EXT_STATE_ACTIVE);
+
+ testStates("listbox-disabled",
+ STATE_UNAVAILABLE, 0,
+ STATE_FOCUSABLE);
+
+ todo(false, "no unavailable state on option in disabled select (bug 759666)");
+// testStates("listitem-disabledlistbox",
+// STATE_UNAVAILABLE, 0,
+// STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE,
+// EXT_STATE_ACTIVE);
+
+ // open combobox
+ gQueue = new eventQueue();
+ gQueue.push(new openComboboxNCheckStates("combobox"));
+ gQueue.invoke(); // Will call */SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=443889"
+ title="mochitest for selects and lists">
+ Mozilla Bug 443889
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=640716"
+ title="mochitest for selects and lists">
+ Mozilla Bug 640716
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=689847"
+ title="Expose active state on current item of selectable widgets">
+ Mozilla Bug 689847
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=756983"
+ title="Isolate focusable and unavailable states from State()">
+ Mozilla Bug 756983
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=907682"
+ title=" HTML:option group position is not correct when select is collapsed">
+ Mozilla Bug 907682
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <select id="combobox">
+ <option>item 1</option>
+ <option>item 2</option>
+ </select>
+
+ <select id="collapsedcombobox">
+ <option id="collapsed-1">item 1</option>
+ <option id="collapsed-2">item 2</option>
+ </select>
+
+ <select id="listbox" name="component" size="3">
+ <option id="listitem-active">Build</option>
+ <option id="listitem">Disability Access APIs</option>
+ <option id="listitem-disabled" disabled>General</option>
+ <optgroup id="listgroup" label="group">
+ <option>option</option>
+ </optgroup>
+ <optgroup id="listgroup-disabled" disabled label="group2">
+ <option id="listitem-disabledgroup">UI</option>
+ </optgroup>
+ </select>
+
+ <select id="listbox-disabled" size="3" disabled>
+ <option id="listitem-disabledlistbox">option</option>
+ </select>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_stale.html b/accessible/tests/mochitest/states/test_stale.html
new file mode 100644
index 000000000..8f85a1995
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_stale.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Stale state testing</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function addChild(aContainerID)
+ {
+ this.containerNode = getNode(aContainerID);
+ this.childNode = null;
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.containerNode)
+ ];
+
+ this.invoke = function addChild_invoke()
+ {
+ this.childNode = document.createElement("div");
+ this.containerNode.appendChild(this.childNode);
+ }
+
+ this.finalCheck = function addChild_finalCheck()
+ {
+ // no stale state should be set
+ testStates(this.childNode, 0, 0, 0, EXT_STATE_STALE);
+ }
+
+ this.getID = function addChild_getID()
+ {
+ return "add child for " + prettyName(aContainerID);
+ }
+ }
+
+ function removeChildChecker(aInvoker)
+ {
+ this.type = EVENT_HIDE;
+ this.__defineGetter__("target", function() { return aInvoker.child; });
+
+ this.check = function removeChildChecker_check()
+ {
+ // stale state should be set
+ testStates(aInvoker.child, 0, EXT_STATE_STALE);
+ }
+ }
+
+ function removeChild(aContainerID)
+ {
+ this.containerNode = getNode(aContainerID);
+ this.child = null;
+
+ this.eventSeq = [
+ new removeChildChecker(this)
+ ];
+
+ this.invoke = function removeChild_invoke()
+ {
+ var childNode = this.containerNode.firstChild;
+ this.child = getAccessible(childNode);
+
+ this.containerNode.removeChild(childNode);
+ }
+
+ this.getID = function removeChild_getID()
+ {
+ return "remove child from " + prettyName(aContainerID);
+ }
+ }
+
+ //gA11yEventDumpToConsole = true; //debugging
+
+ var gQueue = null;
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new addChild("container"));
+ gQueue.push(new removeChild("container"));
+
+ gQueue.invoke(); // will call SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body role="">
+
+ <a target="_blank"
+ title="Expose stale state on accessibles unattached from tree"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=676267">Mozilla Bug 676267</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="container"></div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_tabs.xul b/accessible/tests/mochitest/states/test_tabs.xul
new file mode 100644
index 000000000..a596e178b
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_tabs.xul
@@ -0,0 +1,70 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL tabbox hierarchy tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/MochiKit/packed.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function doTest()
+ {
+ testStates("tab1", 0, EXT_STATE_PINNED);
+ testStates("tab2", 0, 0, 0, EXT_STATE_PINNED);
+ testStates("tab3", 0, 0, 0, EXT_STATE_PINNED);
+
+ SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=577727"
+ title="Make pinned tabs distinguishable from other tabs for accessibility">
+ Mozilla Bug 577727
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <tabbox>
+ <tabs id="tabs">
+ <tab id="tab1" label="tab1" pinned="true"/>
+ <tab id="tab2" label="tab2" pinned="false"/>
+ <tab id="tab3" label="tab3"/>
+ </tabs>
+ <tabpanels id="tabpanels">
+ <tabpanel/>
+ <tabpanel/>
+ </tabpanels>
+ </tabbox>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/states/test_textbox.xul b/accessible/tests/mochitest/states/test_textbox.xul
new file mode 100644
index 000000000..3daf2abe1
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_textbox.xul
@@ -0,0 +1,153 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="nsIAccessible XUL textboxes states tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function getInput(aID)
+ {
+ return getNode(aID).inputField;
+ }
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // Ordinary textbox
+ testStates(getInput("textbox"),
+ STATE_FOCUSABLE,
+ EXT_STATE_EDITABLE,
+ STATE_PROTECTED | STATE_UNAVAILABLE,
+ EXT_STATE_SUPPORTS_AUTOCOMPLETION,
+ "ordinary textbox");
+
+ //////////////////////////////////////////////////////////////////////////
+ // Password textbox
+ testStates(getInput("password"),
+ STATE_FOCUSABLE | STATE_PROTECTED,
+ EXT_STATE_EDITABLE,
+ STATE_UNAVAILABLE,
+ EXT_STATE_SUPPORTS_AUTOCOMPLETION,
+ "password textbox");
+
+ //////////////////////////////////////////////////////////////////////////
+ // Textarea
+ testStates(getInput("textarea"),
+ STATE_FOCUSABLE,
+ EXT_STATE_EDITABLE | EXT_STATE_MULTI_LINE,
+ STATE_PROTECTED | STATE_UNAVAILABLE,
+ EXT_STATE_SUPPORTS_AUTOCOMPLETION,
+ "multiline textbox");
+
+ //////////////////////////////////////////////////////////////////////////
+ // Readonly textbox
+ testStates(getInput("readonly_textbox"),
+ STATE_FOCUSABLE | STATE_READONLY,
+ EXT_STATE_EDITABLE,
+ STATE_PROTECTED | STATE_UNAVAILABLE,
+ EXT_STATE_SUPPORTS_AUTOCOMPLETION,
+ "readonly textbox");
+
+ //////////////////////////////////////////////////////////////////////////
+ // Disabled textbox
+ testStates(getInput("disabled_textbox"),
+ STATE_UNAVAILABLE,
+ EXT_STATE_EDITABLE,
+ STATE_FOCUSABLE | STATE_PROTECTED,
+ EXT_STATE_SUPPORTS_AUTOCOMPLETION,
+ "readonly textbox");
+
+ //////////////////////////////////////////////////////////////////////////
+ // Readonly textarea
+ testStates(getInput("readonly_textarea"),
+ STATE_FOCUSABLE | STATE_READONLY,
+ EXT_STATE_EDITABLE | EXT_STATE_MULTI_LINE,
+ STATE_PROTECTED | STATE_UNAVAILABLE,
+ EXT_STATE_SUPPORTS_AUTOCOMPLETION,
+ "readonly multiline textbox");
+
+ //////////////////////////////////////////////////////////////////////////
+ // Disabled textarea
+ testStates(getInput("disabled_textarea"),
+ STATE_UNAVAILABLE,
+ EXT_STATE_EDITABLE| EXT_STATE_MULTI_LINE,
+ STATE_PROTECTED | STATE_FOCUSABLE,
+ EXT_STATE_SUPPORTS_AUTOCOMPLETION,
+ "readonly multiline textbox");
+
+ //////////////////////////////////////////////////////////////////////////
+ // Search textbox without search button, searches as you type and filters
+ // a separate control.
+ testStates(getInput("searchbox"),
+ STATE_FOCUSABLE,
+ EXT_STATE_EDITABLE | EXT_STATE_SUPPORTS_AUTOCOMPLETION,
+ STATE_PROTECTED | STATE_UNAVAILABLE,
+ 0,
+ "searchbox");
+
+ //////////////////////////////////////////////////////////////////////////
+ // Search textbox with search button, does not support autoCompletion.
+ testStates(getInput("searchfield"),
+ STATE_FOCUSABLE,
+ EXT_STATE_EDITABLE,
+ STATE_PROTECTED | STATE_UNAVAILABLE,
+ EXT_STATE_SUPPORTS_AUTOCOMPLETION,
+ "searchfield");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=442648">
+ Mozilla Bug 442648
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=648235"
+ title="XUL textbox can inherit more states from underlying HTML input">
+ Mozilla Bug 648235
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <textbox id="textbox"/>
+ <textbox id="password" type="password"/>
+ <textbox id="textarea" multiline="true" cols="80" rows="5"/>
+
+ <textbox id="readonly_textbox" readonly="true"/>
+ <textbox id="disabled_textbox" disabled="true"/>
+ <textbox id="readonly_textarea" multiline="true" readonly="true"
+ cols="80" rows="5"/>
+ <textbox id="disabled_textarea" multiline="true" disabled="true"
+ cols="80" rows="5"/>
+
+ <textbox id="searchbox" flex="1" type="search" results="historyTree"/>
+ <textbox id="searchfield" placeholder="Search all add-ons"
+ type="search" searchbutton="true"/>
+ </vbox>
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/states/test_tree.xul b/accessible/tests/mochitest/states/test_tree.xul
new file mode 100644
index 000000000..878a8d25b
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_tree.xul
@@ -0,0 +1,152 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<?xml-stylesheet href="../treeview.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL tree states tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ /**
+ * Event queue invoker object to test accessible states for XUL tree
+ * accessible.
+ */
+ function statesChecker(aTreeID, aView)
+ {
+ this.DOMNode = getNode(aTreeID);
+
+ this.invoke = function statesChecker_invoke()
+ {
+ this.DOMNode.view = aView;
+ }
+
+ this.check = function statesChecker_check()
+ {
+ var tree = getAccessible(this.DOMNode);
+
+ // tree states
+ testStates(tree, STATE_READONLY);
+
+ if (this.DOMNode.getAttribute("seltype") != "single")
+ testStates(tree, STATE_MULTISELECTABLE);
+ else
+ testStates(tree, 0, 0, STATE_MULTISELECTABLE);
+
+ // tree item states
+ var expandedItem = tree.getChildAt(2);
+ testStates(expandedItem,
+ STATE_SELECTABLE | STATE_FOCUSABLE | STATE_EXPANDED);
+
+ var collapsedItem = tree.getChildAt(5);
+ testStates(collapsedItem,
+ STATE_SELECTABLE | STATE_FOCUSABLE | STATE_COLLAPSED);
+
+ // cells states if any
+ var cells = collapsedItem.children;
+ if (cells && cells.length) {
+ for (var idx = 0; idx < cells.length; idx++) {
+ var cell = cells.queryElementAt(idx, nsIAccessible);
+ testStates(cell, STATE_SELECTABLE);
+ }
+
+ var checkboxCell = cells.queryElementAt(3, nsIAccessible);
+ testStates(checkboxCell, STATE_CHECKABLE | STATE_CHECKED);
+ }
+ }
+
+ this.getID = function statesChecker_getID()
+ {
+ return "tree processor for " + prettyName(aTreeID);
+ }
+ }
+
+ gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ gQueue = new eventQueue(EVENT_REORDER);
+ gQueue.push(new statesChecker("tree", new nsTreeTreeView()));
+ gQueue.push(new statesChecker("treesingle", new nsTreeTreeView()));
+ gQueue.push(new statesChecker("tabletree", new nsTreeTreeView()));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ if (MAC && (navigator.userAgent.indexOf("Mac OS X 10.6") != -1)) {
+ todo(false,
+ "Re-enable on Mac OS 10.6 after fixing bug 845095 - intermittent orange");
+ } else {
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ }
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727"
+ title="Reorganize implementation of XUL tree accessibility">
+ Mozilla Bug 503727
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <tree id="treesingle" flex="1" seltype="single">
+ <treecols>
+ <treecol id="col_single" flex="1" primary="true" label="column"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <tree id="tabletree" flex="1" editable="true">
+ <treecols>
+ <treecol id="tabletree_col1" cycler="true" label="cycler"/>
+ <treecol id="tabletree_col2" flex="1" primary="true" label="column1"/>
+ <treecol id="tabletree_col3" flex="1" label="column2"/>
+ <treecol id="tabletree_col4" flex="1" label="checker"
+ type="checkbox" editable="true"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <vbox id="debug"/>
+ </vbox>
+ </hbox>
+
+</window>
diff --git a/accessible/tests/mochitest/states/test_visibility.html b/accessible/tests/mochitest/states/test_visibility.html
new file mode 100644
index 000000000..a2e4a34e6
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_visibility.html
@@ -0,0 +1,175 @@
+<html>
+<head>
+ <title>visibility state testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/MochiKit/packed.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../browser.js"></script>
+
+ <script type="application/javascript">
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function loadURIInvoker(aURI, aFunc)
+ {
+ this.invoke = function loadURIInvoker_invoke()
+ {
+ tabBrowser().loadURI(aURI);
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument)
+ ];
+
+ this.finalCheck = function loadURIInvoker_finalCheck()
+ {
+ aFunc.call();
+ }
+
+ this.getID = function loadURIInvoker_getID()
+ {
+ return "load uri " + aURI;
+ }
+ }
+
+ function addTabInvoker(aURL, aFunc)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 1)
+ ];
+
+ this.invoke = function addTabInvoker_invoke()
+ {
+ tabBrowser().loadOneTab(aURL, null, "", null, false);
+ }
+
+ this.finalCheck = function addTabInvoker_finalCheck()
+ {
+ aFunc.call();
+ }
+
+ this.getID = function addTabInvoker_getID()
+ {
+ return "add tab: " + aURL;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Tests
+
+ function testBackgroundTab()
+ {
+ // Accessibles in background tab should have offscreen state and no
+ // invisible state.
+ var tabDoc = tabDocumentAt(0);
+ var input = getAccessible(tabDoc.getElementById("input"));
+ testStates(input, STATE_OFFSCREEN, 0, STATE_INVISIBLE);
+ }
+
+ function testScrolledOff()
+ {
+ var tabDoc = tabDocumentAt(1);
+
+ // scrolled off
+ input = getAccessible(tabDoc.getElementById("input_scrolledoff"));
+ testStates(input, STATE_OFFSCREEN, 0, STATE_INVISIBLE);
+
+ // scrolled off item (twice)
+ var lastLiNode = tabDoc.getElementById("li_last");
+ var lastLi = getAccessible(lastLiNode);
+ testStates(lastLi, STATE_OFFSCREEN, 0, STATE_INVISIBLE);
+
+ // scroll into view the item
+ lastLiNode.scrollIntoView(true);
+ testStates(lastLi, 0, 0, STATE_OFFSCREEN | STATE_INVISIBLE);
+
+ // first item is scrolled off now (testcase for bug 768786)
+ var firstLi = getAccessible(tabDoc.getElementById("li_first"));
+ testStates(firstLi, STATE_OFFSCREEN, 0, STATE_INVISIBLE);
+ }
+
+ var gInputDocURI = "data:text/html,<html><body>";
+ gInputDocURI += "<input id='input'></body></html>";
+
+ var gDocURI = "data:text/html,<html><body>";
+ gDocURI += "<div style='border:2px solid blue; width: 500px; height: 600px;'></div>";
+ gDocURI += "<input id='input_scrolledoff'>";
+ gDocURI += "<ul style='border:2px solid red; width: 100px; height: 50px; overflow: auto;'>";
+ gDocURI += " <li id='li_first'>item1</li><li>item2</li><li>item3</li>";
+ gDocURI += " <li>item4</li><li>item5</li><li id='li_last'>item6</li>";
+ gDocURI += "</ul>";
+ gDocURI += "</body></html>";
+
+ function doTests()
+ {
+ testStates("div", 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN);
+ testStates("div_off", STATE_OFFSCREEN, 0, STATE_INVISIBLE);
+ testStates("div_transformed", STATE_OFFSCREEN, 0, STATE_INVISIBLE);
+ testStates("div_abschild", 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN);
+
+ gQueue = new eventQueue();
+
+ gQueue.push(new addTabInvoker("about:blank", testBackgroundTab));
+ gQueue.push(new loadURIInvoker(gDocURI, testScrolledOff));
+
+ gQueue.onFinish = function() { closeBrowserWindow(); }
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ openBrowserWindow(doTests, gInputDocURI, { width: 600, height: 600 });
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=591363"
+ title="(in)visible state is not always correct?">
+ Mozilla Bug 591363
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=768786"
+ title="Offscreen state is not exposed under certain circumstances">
+ Mozilla Bug 768786
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="outer_div">
+
+ <!-- trivial cases -->
+ <div id="div">div</div>
+ <div id="div_off" style="position: absolute; left:-999px; top:-999px">
+ offscreen!
+ </div>
+ <div id="div_transformed" style="transform: translate(-999px, -999px);">
+ transformed!
+ </div>
+
+ <!-- edge case: no rect but has out of flow child -->
+ <div id="div_abschild">
+ <p style="position: absolute; left: 120px; top:120px;">absolute</p>
+ </div>
+
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/states/test_visibility.xul b/accessible/tests/mochitest/states/test_visibility.xul
new file mode 100644
index 000000000..8b2ac990c
--- /dev/null
+++ b/accessible/tests/mochitest/states/test_visibility.xul
@@ -0,0 +1,152 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL elements visibility states testing">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function openMenu(aID, aSubID, aOffscreenSubID)
+ {
+ this.menuNode = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, this.menuNode)
+ ];
+
+ this.invoke = function openMenu_invoke()
+ {
+ this.menuNode.open = true;
+ }
+
+ this.finalCheck = function openMenu_finalCheck()
+ {
+ testStates(aID, 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN);
+ testStates(aSubID, 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN);
+ if (aOffscreenSubID)
+ testStates(aOffscreenSubID, STATE_OFFSCREEN, 0, STATE_INVISIBLE);
+ }
+
+ this.getID = function openMenu_invoke()
+ {
+ return "open menu '" + aID + "' and test states";
+ }
+ }
+
+ function closeMenu(aID, aSubID, aSub2ID)
+ {
+ this.menuNode = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, document)
+ ];
+
+ this.invoke = function openMenu_invoke()
+ {
+ this.menuNode.open = false;
+ }
+
+ this.finalCheck = function openMenu_finalCheck()
+ {
+ testStates(aID, 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN);
+ testStates(aSubID, STATE_INVISIBLE, 0, STATE_OFFSCREEN);
+ testStates(aSub2ID, STATE_INVISIBLE, 0, STATE_OFFSCREEN);
+ }
+
+ this.getID = function openMenu_invoke()
+ {
+ return "open menu and test states";
+ }
+ }
+
+ var gQueue = null;
+ function doTest()
+ {
+ testStates("deck_pane2", 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN);
+ testStates("tabs_pane1", 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN);
+ testStates("tabs_pane2", STATE_OFFSCREEN, 0, STATE_INVISIBLE);
+
+ gQueue = new eventQueue();
+ gQueue.push(new openMenu("mi_file1", "mi_file1.1"));
+ gQueue.push(new openMenu("mi_file1.2", "mi_file1.2.1", "mi_file1.2.4"));
+ gQueue.push(new closeMenu("mi_file1", "mi_file1.1", "mi_file1.2.1"));
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=810260"
+ title="xul:deck hidden pages shouldn't be offscreen">
+ Mozilla Bug 810260
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=865591"
+ title="Visible menu item have offscreen state">
+ Mozilla Bug 865591
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+
+ <deck selectedIndex="1">
+ <description value="This is the first page" id="deck_pane1"/>
+ <button label="This is the second page" id="deck_pane2"/>
+ </deck>
+
+ <tabbox>
+ <tabs>
+ <tab>tab1</tab>
+ <tab>tab2</tab>
+ </tabs>
+ <tabpanels>
+ <description value="This is the first page" id="tabs_pane1"/>
+ <button label="This is the second page" id="tabs_pane2"/>
+ </tabpanels>
+ </tabbox>
+
+ <menubar>
+ <menu label="File" id="mi_file1">
+ <menupopup>
+ <menuitem label="SubFile" id="mi_file1.1"/>
+ <menu label="SubFile2" id="mi_file1.2">
+ <menupopup style="max-height: 5em;">
+ <menuitem label="SubSubFile" id="mi_file1.2.1"/>
+ <menuitem label="SubSubFile2" id="mi_file1.2.2"/>
+ <menuitem label="SubSubFile3" id="mi_file1.2.3"/>
+ <menuitem label="SubSubFile4" id="mi_file1.2.4"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+ </menu>
+ </menubar>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/states/z_frames.html b/accessible/tests/mochitest/states/z_frames.html
new file mode 100644
index 000000000..819adee63
--- /dev/null
+++ b/accessible/tests/mochitest/states/z_frames.html
@@ -0,0 +1,11 @@
+<html>
+<!--
+Auxilliary file used as frame source.
+-->
+<head>
+</head>
+<body>
+<p>Frame source body has no role</p>
+</body>
+</html>
+
diff --git a/accessible/tests/mochitest/states/z_frames_article.html b/accessible/tests/mochitest/states/z_frames_article.html
new file mode 100644
index 000000000..a7a69b4da
--- /dev/null
+++ b/accessible/tests/mochitest/states/z_frames_article.html
@@ -0,0 +1,11 @@
+<html>
+<!--
+Auxilliary file used as frame source.
+-->
+<head>
+</head>
+<body role="article">
+<p>Article</p>
+</body>
+</html>
+
diff --git a/accessible/tests/mochitest/states/z_frames_checkbox.html b/accessible/tests/mochitest/states/z_frames_checkbox.html
new file mode 100644
index 000000000..799764424
--- /dev/null
+++ b/accessible/tests/mochitest/states/z_frames_checkbox.html
@@ -0,0 +1,11 @@
+<html>
+<!--
+Auxilliary file used as frame source.
+-->
+<head>
+</head>
+<body role="checkbox">
+<p>Checkbox</p>
+</body>
+</html>
+
diff --git a/accessible/tests/mochitest/states/z_frames_textbox.html b/accessible/tests/mochitest/states/z_frames_textbox.html
new file mode 100644
index 000000000..0f4e1b9d6
--- /dev/null
+++ b/accessible/tests/mochitest/states/z_frames_textbox.html
@@ -0,0 +1,11 @@
+<html>
+<!--
+Auxilliary file used as frame source.
+-->
+<head>
+</head>
+<body role="textbox">
+<p>Texbox</p>
+</body>
+</html>
+
diff --git a/accessible/tests/mochitest/states/z_frames_update.html b/accessible/tests/mochitest/states/z_frames_update.html
new file mode 100644
index 000000000..90d756afe
--- /dev/null
+++ b/accessible/tests/mochitest/states/z_frames_update.html
@@ -0,0 +1,22 @@
+<html>
+<head>
+<script>
+function replaceBody()
+{
+ var accService = Components.classes["@mozilla.org/accessibilityService;1"].
+ getService(Components.interfaces.nsIAccessibilityService);
+ accService.getAccessibleFor(document);
+
+ var newBody = document.createElement("body");
+ newBody.setAttribute("contentEditable", "true");
+ newBody.textContent = "New Hello";
+ document.documentElement.replaceChild(newBody, document.body);
+ getComputedStyle(newBody, "").color;
+}
+</script>
+</head>
+<body onload="replaceBody();">
+OLD hello
+</body>
+</html>
+
diff --git a/accessible/tests/mochitest/table.js b/accessible/tests/mochitest/table.js
new file mode 100644
index 000000000..e171594d6
--- /dev/null
+++ b/accessible/tests/mochitest/table.js
@@ -0,0 +1,778 @@
+/**
+ * This file provides set of helper functions to test nsIAccessibleTable
+ * interface.
+ *
+ * Required:
+ * common.js
+ * role.js
+ * states.js
+ */
+
+/**
+ * Constants used to describe cells array.
+ */
+const kDataCell = 1; // Indicates the cell is origin data cell
+const kRowHeaderCell = 2; // Indicates the cell is row header cell
+const kColHeaderCell = 4; // Indicated the cell is column header cell
+const kOrigin = kDataCell | kRowHeaderCell | kColHeaderCell;
+
+const kRowSpanned = 8; // Indicates the cell is not origin and row spanned
+const kColSpanned = 16; // Indicates the cell is not origin and column spanned
+const kSpanned = kRowSpanned | kColSpanned;
+
+/**
+ * Constants to define column header type.
+ */
+const kNoColumnHeader = 0;
+const kListboxColumnHeader = 1;
+const kTreeColumnHeader = 2;
+
+/**
+ * Constants to define table type.
+ */
+const kTable = 0;
+const kTreeTable = 1;
+const kMathTable = 2;
+
+/**
+ * Test table structure and related methods.
+ *
+ * @param aIdentifier [in] table accessible identifier
+ * @param aCellsArray [in] two dimensional array (row X columns) of
+ * cell types (see constants defined above).
+ * @param aColHeaderType [in] specifies wether column header cells are
+ * arranged into the list.
+ * @param aCaption [in] caption text if any
+ * @param aSummary [in] summary text if any
+ * @param aTableType [in] specifies the table type.
+ * @param aRowRoles [in] array of row roles.
+ */
+function testTableStruct(aIdentifier, aCellsArray, aColHeaderType,
+ aCaption, aSummary, aTableType, aRowRoles)
+{
+ var tableNode = getNode(aIdentifier);
+ var isGrid = tableNode.getAttribute("role") == "grid" ||
+ tableNode.getAttribute("role") == "treegrid" ||
+ tableNode.localName == "tree";
+
+ var rowCount = aCellsArray.length;
+ var colsCount = aCellsArray[0] ? aCellsArray[0].length : 0;
+
+ // Test table accessible tree.
+ var tableObj = {
+ children: []
+ };
+ switch (aTableType) {
+ case kTable:
+ tableObj.role = ROLE_TABLE;
+ break;
+ case kTreeTable:
+ tableObj.role = ROLE_TREE_TABLE;
+ break;
+ case kMathTable:
+ tableObj.role = ROLE_MATHML_TABLE;
+ break;
+ }
+
+ // caption accessible handling
+ if (aCaption) {
+ var captionObj = {
+ role: ROLE_CAPTION,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: aCaption
+ }
+ ]
+ };
+
+ tableObj.children.push(captionObj);
+ }
+
+ // special types of column headers handling
+ if (aColHeaderType) {
+ var headersObj = {
+ role: ROLE_LIST,
+ children: []
+ };
+
+ for (var idx = 0; idx < colsCount; idx++) {
+ var headerCellObj = {
+ role: ROLE_COLUMNHEADER
+ };
+ headersObj.children.push(headerCellObj);
+ }
+
+ if (aColHeaderType == kTreeColumnHeader) {
+ var columnPickerObj = {
+ role: ROLE_PUSHBUTTON
+ };
+
+ headersObj.children.push(columnPickerObj);
+ }
+
+ tableObj.children.push(headersObj);
+ }
+
+ // rows and cells accessibles
+ for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ var rowObj = {
+ role: aRowRoles ? aRowRoles[rowIdx] : ROLE_ROW,
+ children: []
+ };
+
+ for (var colIdx = 0; colIdx < colsCount; colIdx++) {
+ var celltype = aCellsArray[rowIdx][colIdx];
+
+ var role = ROLE_NOTHING;
+ switch (celltype) {
+ case kDataCell:
+ role = (aTableType == kMathTable ? ROLE_MATHML_CELL :
+ (isGrid ? ROLE_GRID_CELL : ROLE_CELL));
+ break;
+ case kRowHeaderCell:
+ role = ROLE_ROWHEADER;
+ break;
+ case kColHeaderCell:
+ role = ROLE_COLUMNHEADER;
+ break;
+ }
+
+ if (role != ROLE_NOTHING) {
+ var cellObj = {
+ role: role
+ };
+ rowObj.children.push(cellObj);
+ }
+ }
+
+ tableObj.children.push(rowObj);
+ }
+
+ testAccessibleTree(aIdentifier, tableObj);
+
+ // Test table table interface.
+ var table = getAccessible(aIdentifier, [nsIAccessibleTable]);
+
+ // summary
+ if (aSummary)
+ is(table.summary, aSummary,
+ "Wrong summary of the table " + prettyName(aIdentifier));
+
+ // rowCount and columnCount
+ is(table.rowCount, rowCount,
+ "Wrong rows count of " + prettyName(aIdentifier));
+ is(table.columnCount, colsCount,
+ "Wrong columns count of " + prettyName(aIdentifier));
+
+ // rows and columns extents
+ for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ for (var colIdx = 0; colIdx < colsCount; colIdx++) {
+ var celltype = aCellsArray[rowIdx][colIdx];
+ if (celltype & kOrigin) {
+
+ // table getRowExtentAt
+ var rowExtent = table.getRowExtentAt(rowIdx, colIdx);
+ for (var idx = rowIdx + 1;
+ idx < rowCount && (aCellsArray[idx][colIdx] & kRowSpanned);
+ idx++);
+
+ var expectedRowExtent = idx - rowIdx;
+ is(rowExtent, expectedRowExtent,
+ "getRowExtentAt: Wrong number of spanned rows at (" + rowIdx + ", " +
+ colIdx + ") for " + prettyName(aIdentifier));
+
+ // table getColumnExtentAt
+ var colExtent = table.getColumnExtentAt(rowIdx, colIdx);
+ for (var idx = colIdx + 1;
+ idx < colsCount && (aCellsArray[rowIdx][idx] & kColSpanned);
+ idx++);
+
+ var expectedColExtent = idx - colIdx;
+ is(colExtent, expectedColExtent,
+ "getColumnExtentAt: Wrong number of spanned columns at (" + rowIdx +
+ ", " + colIdx + ") for " + prettyName(aIdentifier));
+
+ // cell rowExtent and columnExtent
+ var cell = getAccessible(table.getCellAt(rowIdx, colIdx),
+ [nsIAccessibleTableCell]);
+
+ is(cell.rowExtent, expectedRowExtent,
+ "rowExtent: Wrong number of spanned rows at (" + rowIdx + ", " +
+ colIdx + ") for " + prettyName(aIdentifier));
+
+ is(cell.columnExtent, expectedColExtent,
+ "columnExtent: Wrong number of spanned column at (" + rowIdx + ", " +
+ colIdx + ") for " + prettyName(aIdentifier));
+ }
+ }
+ }
+}
+
+/**
+ * Test table indexes.
+ *
+ * @param aIdentifier [in] table accessible identifier
+ * @param aIdxes [in] two dimensional array of cell indexes
+ */
+function testTableIndexes(aIdentifier, aIdxes)
+{
+ var tableAcc = getAccessible(aIdentifier, [nsIAccessibleTable]);
+ if (!tableAcc)
+ return;
+
+ var obtainedRowIdx, obtainedColIdx, obtainedIdx;
+ var cellAcc;
+
+ var id = prettyName(aIdentifier);
+
+ var rowCount = aIdxes.length;
+ for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ var colCount = aIdxes[rowIdx].length;
+ for (var colIdx = 0; colIdx < colCount; colIdx++) {
+ var idx = aIdxes[rowIdx][colIdx];
+
+ // getCellAt
+ try {
+ cellAcc = null;
+ cellAcc = tableAcc.getCellAt(rowIdx, colIdx);
+ } catch (e) { }
+
+ ok(idx != -1 && cellAcc || idx == -1 && !cellAcc,
+ id + ": Can't get cell accessible at row = " + rowIdx + ", column = " + colIdx);
+
+ if (idx != - 1) {
+
+ // getRowIndexAt
+ var origRowIdx = rowIdx;
+ while (origRowIdx > 0 &&
+ aIdxes[rowIdx][colIdx] == aIdxes[origRowIdx - 1][colIdx])
+ origRowIdx--;
+
+ try {
+ obtainedRowIdx = tableAcc.getRowIndexAt(idx);
+ } catch (e) {
+ ok(false, id + ": can't get row index for cell index " + idx + "," + e);
+ }
+
+ is(obtainedRowIdx, origRowIdx,
+ id + ": row for index " + idx + " is not correct (getRowIndexAt)");
+
+ // getColumnIndexAt
+ var origColIdx = colIdx;
+ while (origColIdx > 0 &&
+ aIdxes[rowIdx][colIdx] == aIdxes[rowIdx][origColIdx - 1])
+ origColIdx--;
+
+ try {
+ obtainedColIdx = tableAcc.getColumnIndexAt(idx);
+ } catch (e) {
+ ok(false, id + ": can't get column index for cell index " + idx + "," + e);
+ }
+
+ is(obtainedColIdx, origColIdx,
+ id + ": column for index " + idx + " is not correct (getColumnIndexAt)");
+
+ // getRowAndColumnIndicesAt
+ var obtainedRowIdxObj = { }, obtainedColIdxObj = { };
+ try {
+ tableAcc.getRowAndColumnIndicesAt(idx, obtainedRowIdxObj,
+ obtainedColIdxObj);
+ } catch (e) {
+ ok(false, id + ": can't get row and column indices for cell index " + idx + "," + e);
+ }
+
+ is(obtainedRowIdxObj.value, origRowIdx,
+ id + ": row for index " + idx + " is not correct (getRowAndColumnIndicesAt)");
+ is(obtainedColIdxObj.value, origColIdx,
+ id + ": column for index " + idx + " is not correct (getRowAndColumnIndicesAt)");
+
+ if (cellAcc) {
+
+ var cellId = prettyName(cellAcc);
+ cellAcc = getAccessible(cellAcc, [nsIAccessibleTableCell]);
+
+ // cell: 'table-cell-index' attribute
+ var attrs = cellAcc.attributes;
+ var strIdx = "";
+ try {
+ strIdx = attrs.getStringProperty("table-cell-index");
+ } catch (e) {
+ ok(false,
+ cellId + ": no cell index from object attributes on the cell accessible at index " + idx + ".");
+ }
+
+ if (strIdx) {
+ is (parseInt(strIdx), idx,
+ cellId + ": cell index from object attributes of cell accessible isn't corrent.");
+ }
+
+ // cell: table
+ try {
+ is(cellAcc.table, tableAcc,
+ cellId + ": wrong table accessible for the cell.");
+
+ } catch (e) {
+ ok(false,
+ cellId + ": can't get table accessible from the cell.");
+ }
+
+ // cell: getRowIndex
+ try {
+ obtainedRowIdx = cellAcc.rowIndex;
+ } catch (e) {
+ ok(false,
+ cellId + ": can't get row index of the cell at index " + idx + "," + e);
+ }
+
+ is(obtainedRowIdx, origRowIdx,
+ cellId + ": row for the cell at index " + idx +" is not correct");
+
+ // cell: getColumnIndex
+ try {
+ obtainedColIdx = cellAcc.columnIndex;
+ } catch (e) {
+ ok(false,
+ cellId + ": can't get column index of the cell at index " + idx + "," + e);
+ }
+
+ is(obtainedColIdx, origColIdx,
+ id + ": column for the cell at index " + idx +" is not correct");
+ }
+ }
+
+ // getCellIndexAt
+ try {
+ obtainedIdx = tableAcc.getCellIndexAt(rowIdx, colIdx);
+ } catch (e) {
+ obtainedIdx = -1;
+ }
+
+ is(obtainedIdx, idx,
+ id + ": row " + rowIdx + " /column " + colIdx + " and index " + obtainedIdx + " aren't inconsistent.");
+ }
+ }
+}
+
+/**
+ * Test table getters selection methods.
+ *
+ * @param aIdentifier [in] table accessible identifier
+ * @param aCellsArray [in] two dimensional array (row X columns) of cells
+ * states (either boolean (selected/unselected) if cell is
+ * origin, otherwise kRowSpanned or kColSpanned constant).
+ * @param aMsg [in] text appended before every message
+ */
+function testTableSelection(aIdentifier, aCellsArray, aMsg)
+{
+ var msg = aMsg ? aMsg : "";
+ var acc = getAccessible(aIdentifier, [nsIAccessibleTable]);
+ if (!acc)
+ return;
+
+ var rowCount = aCellsArray.length;
+ var colsCount = aCellsArray[0].length;
+
+ // Columns selection tests.
+ var selCols = new Array();
+
+ // isColumnSelected test
+ for (var colIdx = 0; colIdx < colsCount; colIdx++) {
+ var isColSelected = true;
+ for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ if (aCellsArray[rowIdx][colIdx] == false ||
+ aCellsArray[rowIdx][colIdx] == undefined) {
+ isColSelected = false;
+ break;
+ }
+ }
+
+ is(acc.isColumnSelected(colIdx), isColSelected,
+ msg + "Wrong selection state of " + colIdx + " column for " +
+ prettyName(aIdentifier));
+
+ if (isColSelected)
+ selCols.push(colIdx);
+ }
+
+ // selectedColsCount test
+ is(acc.selectedColumnCount, selCols.length,
+ msg + "Wrong count of selected columns for " + prettyName(aIdentifier));
+
+ // getSelectedColumns test
+ var actualSelColsCountObj = { value: null };
+ var actualSelCols = acc.getSelectedColumnIndices(actualSelColsCountObj);
+
+ var actualSelColsCount = actualSelColsCountObj.value;
+ is (actualSelColsCount, selCols.length,
+ msg + "Wrong count of selected columns for " + prettyName(aIdentifier) +
+ "from getSelectedColumns.");
+
+ for (var i = 0; i < actualSelColsCount; i++) {
+ is (actualSelCols[i], selCols[i],
+ msg + "Column at index " + selCols[i] + " should be selected.");
+ }
+
+ // Rows selection tests.
+ var selRows = new Array();
+
+ // isRowSelected test
+ var selrowCount = 0;
+ for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ var isRowSelected = true;
+ for (var colIdx = 0; colIdx < colsCount; colIdx++) {
+ if (aCellsArray[rowIdx][colIdx] == false ||
+ aCellsArray[rowIdx][colIdx] == undefined) {
+ isRowSelected = false;
+ break;
+ }
+ }
+
+ is(acc.isRowSelected(rowIdx), isRowSelected,
+ msg + "Wrong selection state of " + rowIdx + " row for " +
+ prettyName(aIdentifier));
+
+ if (isRowSelected)
+ selRows.push(rowIdx);
+ }
+
+ // selectedRowCount test
+ is(acc.selectedRowCount, selRows.length,
+ msg + "Wrong count of selected rows for " + prettyName(aIdentifier));
+
+ // getSelectedRows test
+ var actualSelrowCountObj = { value: null };
+ var actualSelRows = acc.getSelectedRowIndices(actualSelrowCountObj);
+
+ var actualSelrowCount = actualSelrowCountObj.value;
+ is (actualSelrowCount, selRows.length,
+ msg + "Wrong count of selected rows for " + prettyName(aIdentifier) +
+ "from getSelectedRows.");
+
+ for (var i = 0; i < actualSelrowCount; i++) {
+ is (actualSelRows[i], selRows[i],
+ msg + "Row at index " + selRows[i] + " should be selected.");
+ }
+
+ // Cells selection tests.
+ var selCells = new Array();
+
+ // isCellSelected test
+ for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ for (var colIdx = 0; colIdx < colsCount; colIdx++) {
+ if (aCellsArray[rowIdx][colIdx] & kSpanned)
+ continue;
+
+ var isSelected = aCellsArray[rowIdx][colIdx] == true;
+ is(acc.isCellSelected(rowIdx, colIdx), isSelected,
+ msg + "Wrong selection state of cell at " + rowIdx + " row and " +
+ colIdx + " column for " + prettyName(aIdentifier));
+
+ if (aCellsArray[rowIdx][colIdx])
+ selCells.push(acc.getCellIndexAt(rowIdx, colIdx));
+ }
+ }
+
+ // selectedCellCount tests
+ is(acc.selectedCellCount, selCells.length,
+ msg + "Wrong count of selected cells for " + prettyName(aIdentifier));
+
+ // getSelectedCellIndices test
+ var actualSelCellsCountObj = { value: null };
+ var actualSelCells = acc.getSelectedCellIndices(actualSelCellsCountObj);
+
+ var actualSelCellsCount = actualSelCellsCountObj.value;
+ is(actualSelCellsCount, selCells.length,
+ msg + "Wrong count of selected cells for " + prettyName(aIdentifier) +
+ "from getSelectedCells.");
+
+ for (var i = 0; i < actualSelCellsCount; i++) {
+ is(actualSelCells[i], selCells[i],
+ msg + "getSelectedCellIndices: Cell at index " + selCells[i] +
+ " should be selected.");
+ }
+
+ // selectedCells and isSelected tests
+ var actualSelCellsArray = acc.selectedCells;
+ for (var i = 0; i < actualSelCellsCount; i++) {
+ var actualSelCellAccessible =
+ actualSelCellsArray.queryElementAt(i, nsIAccessibleTableCell);
+
+ var colIdx = acc.getColumnIndexAt(selCells[i]);
+ var rowIdx = acc.getRowIndexAt(selCells[i]);
+ var expectedSelCellAccessible = acc.getCellAt(rowIdx, colIdx);
+
+ ok(actualSelCellAccessible, expectedSelCellAccessible,
+ msg + "getSelectedCells: Cell at index " + selCells[i] +
+ " should be selected.");
+
+ ok(actualSelCellAccessible.isSelected(),
+ "isSelected: Cell at index " + selCells[i] + " should be selected.");
+ }
+
+ // selected states tests
+ for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ for (var colIdx = 0; colIdx < colsCount; colIdx++) {
+ if (aCellsArray[rowIdx][colIdx] & kSpanned)
+ continue;
+
+ var cell = acc.getCellAt(rowIdx, colIdx);
+ var isSel = aCellsArray[rowIdx][colIdx];
+ if (isSel == undefined)
+ testStates(cell, 0, 0, STATE_SELECTABLE | STATE_SELECTED);
+ else if (isSel == true)
+ testStates(cell, STATE_SELECTED);
+ else
+ testStates(cell, STATE_SELECTABLE, 0, STATE_SELECTED);
+ }
+ }
+}
+
+/**
+ * Test unselectColumn method of accessible table.
+ */
+function testUnselectTableColumn(aIdentifier, aColIdx, aCellsArray)
+{
+ var acc = getAccessible(aIdentifier, [nsIAccessibleTable]);
+ if (!acc)
+ return;
+
+ var rowCount = aCellsArray.length;
+ for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ var cellState = aCellsArray[rowIdx][aColIdx];
+ // Unselect origin cell.
+ var [origRowIdx, origColIdx] =
+ getOrigRowAndColumn(aCellsArray, rowIdx, aColIdx);
+ aCellsArray[origRowIdx][origColIdx] = false;
+ }
+
+ acc.unselectColumn(aColIdx);
+ testTableSelection(aIdentifier, aCellsArray,
+ "Unselect " + aColIdx + " column: ");
+}
+
+/**
+ * Test selectColumn method of accessible table.
+ */
+function testSelectTableColumn(aIdentifier, aColIdx, aCellsArray)
+{
+ var acc = getAccessible(aIdentifier, [nsIAccessibleTable]);
+ if (!acc)
+ return;
+
+ var rowCount = aCellsArray.length;
+ var colsCount = aCellsArray[0].length;
+
+ for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ for (var colIdx = 0; colIdx < colsCount; colIdx++) {
+ var cellState = aCellsArray[rowIdx][colIdx];
+
+ if (colIdx == aColIdx) { // select target column
+ if (!(cellState & kSpanned)) {
+ // Select the cell if it is origin.
+ aCellsArray[rowIdx][colIdx] = true;
+
+ } else {
+ // If the cell is spanned then search origin cell and select it.
+ var [origRowIdx, origColIdx] = getOrigRowAndColumn(aCellsArray,
+ rowIdx, colIdx);
+ aCellsArray[origRowIdx][origColIdx] = true;
+ }
+
+ } else if (!(cellState & kSpanned)) { // unselect other columns
+ if (colIdx > aColIdx) {
+ // Unselect the cell if traversed column index is greater than column
+ // index of target cell.
+ aCellsArray[rowIdx][colIdx] = false;
+
+ } else if (!(aCellsArray[rowIdx][aColIdx] & kColSpanned)) {
+ // Unselect the cell if the target cell is not row spanned.
+ aCellsArray[rowIdx][colIdx] = false;
+
+ } else {
+ // Unselect the cell if it is not spanned to the target cell.
+ for (var spannedColIdx = colIdx + 1; spannedColIdx < aColIdx;
+ spannedColIdx++) {
+ var spannedCellState = aCellsArray[rowIdx][spannedColIdx];
+ if (!(spannedCellState & kRowSpanned)) {
+ aCellsArray[rowIdx][colIdx] = false;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ acc.selectColumn(aColIdx);
+ testTableSelection(aIdentifier, aCellsArray,
+ "Select " + aColIdx + " column: ");
+}
+
+/**
+ * Test unselectRow method of accessible table.
+ */
+function testUnselectTableRow(aIdentifier, aRowIdx, aCellsArray)
+{
+ var acc = getAccessible(aIdentifier, [nsIAccessibleTable]);
+ if (!acc)
+ return;
+
+ var colsCount = aCellsArray[0].length;
+ for (var colIdx = 0; colIdx < colsCount; colIdx++) {
+ // Unselect origin cell.
+ var [origRowIdx, origColIdx] = getOrigRowAndColumn(aCellsArray,
+ aRowIdx, colIdx);
+ aCellsArray[origRowIdx][origColIdx] = false;
+ }
+
+ acc.unselectRow(aRowIdx);
+ testTableSelection(aIdentifier, aCellsArray,
+ "Unselect " + aRowIdx + " row: ");
+}
+
+/**
+ * Test selectRow method of accessible table.
+ */
+function testSelectTableRow(aIdentifier, aRowIdx, aCellsArray)
+{
+ var acc = getAccessible(aIdentifier, [nsIAccessibleTable]);
+ if (!acc)
+ return;
+
+ var rowCount = aCellsArray.length;
+ var colsCount = aCellsArray[0].length;
+
+ for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ for (var colIdx = 0; colIdx < colsCount; colIdx++) {
+ var cellState = aCellsArray[rowIdx][colIdx];
+
+ if (rowIdx == aRowIdx) { // select the given row
+ if (!(cellState & kSpanned)) {
+ // Select the cell if it is origin.
+ aCellsArray[rowIdx][colIdx] = true;
+
+ } else {
+ // If the cell is spanned then search origin cell and select it.
+ var [origRowIdx, origColIdx] = getOrigRowAndColumn(aCellsArray,
+ rowIdx, colIdx);
+
+ aCellsArray[origRowIdx][origColIdx] = true;
+ }
+
+ } else if (!(cellState & kSpanned)) { // unselect other rows
+ if (rowIdx > aRowIdx) {
+ // Unselect the cell if traversed row index is greater than row
+ // index of target cell.
+ aCellsArray[rowIdx][colIdx] = false;
+
+ } else if (!(aCellsArray[aRowIdx][colIdx] & kRowSpanned)) {
+ // Unselect the cell if the target cell is not row spanned.
+ aCellsArray[rowIdx][colIdx] = false;
+
+ } else {
+ // Unselect the cell if it is not spanned to the target cell.
+ for (var spannedRowIdx = rowIdx + 1; spannedRowIdx < aRowIdx;
+ spannedRowIdx++) {
+ var spannedCellState = aCellsArray[spannedRowIdx][colIdx];
+ if (!(spannedCellState & kRowSpanned)) {
+ aCellsArray[rowIdx][colIdx] = false;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ acc.selectRow(aRowIdx);
+ testTableSelection(aIdentifier, aCellsArray,
+ "Select " + aRowIdx + " row: ");
+}
+
+/**
+ * Test columnHeaderCells and rowHeaderCells of accessible table.
+ */
+function testHeaderCells(aHeaderInfoMap)
+{
+ for (var testIdx = 0; testIdx < aHeaderInfoMap.length; testIdx++) {
+ var dataCellIdentifier = aHeaderInfoMap[testIdx].cell;
+ var dataCell = getAccessible(dataCellIdentifier, [nsIAccessibleTableCell]);
+
+ // row header cells
+ var rowHeaderCells = aHeaderInfoMap[testIdx].rowHeaderCells;
+ var rowHeaderCellsCount = rowHeaderCells.length;
+ var actualRowHeaderCells = dataCell.rowHeaderCells;
+ var actualRowHeaderCellsCount = actualRowHeaderCells.length;
+
+ is(actualRowHeaderCellsCount, rowHeaderCellsCount,
+ "Wrong number of row header cells for the cell " +
+ prettyName(dataCellIdentifier));
+
+ if (actualRowHeaderCellsCount == rowHeaderCellsCount) {
+ for (var idx = 0; idx < rowHeaderCellsCount; idx++) {
+ var rowHeaderCell = getAccessible(rowHeaderCells[idx]);
+ var actualRowHeaderCell =
+ actualRowHeaderCells.queryElementAt(idx, nsIAccessible);
+ isObject(actualRowHeaderCell, rowHeaderCell,
+ "Wrong row header cell at index " + idx + " for the cell " +
+ dataCellIdentifier);
+ }
+ }
+
+ // column header cells
+ var colHeaderCells = aHeaderInfoMap[testIdx].columnHeaderCells;
+ var colHeaderCellsCount = colHeaderCells.length;
+ var actualColHeaderCells = dataCell.columnHeaderCells;
+ var actualColHeaderCellsCount = actualColHeaderCells.length;
+
+ is(actualColHeaderCellsCount, colHeaderCellsCount,
+ "Wrong number of column header cells for the cell " +
+ prettyName(dataCellIdentifier));
+
+ if (actualColHeaderCellsCount == colHeaderCellsCount) {
+ for (var idx = 0; idx < colHeaderCellsCount; idx++) {
+ var colHeaderCell = getAccessible(colHeaderCells[idx]);
+ var actualColHeaderCell =
+ actualColHeaderCells.queryElementAt(idx, nsIAccessible);
+ isObject(actualColHeaderCell, colHeaderCell,
+ "Wrong column header cell at index " + idx + " for the cell " +
+ dataCellIdentifier);
+ }
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// private implementation
+
+/**
+ * Return row and column of orig cell for the given spanned cell.
+ */
+function getOrigRowAndColumn(aCellsArray, aRowIdx, aColIdx)
+{
+ var cellState = aCellsArray[aRowIdx][aColIdx];
+
+ var origRowIdx = aRowIdx, origColIdx = aColIdx;
+ if (cellState & kRowSpanned) {
+ for (var prevRowIdx = aRowIdx - 1; prevRowIdx >= 0; prevRowIdx--) {
+ var prevCellState = aCellsArray[prevRowIdx][aColIdx];
+ if (!(prevCellState & kRowSpanned)) {
+ origRowIdx = prevRowIdx;
+ break;
+ }
+ }
+ }
+
+ if (cellState & kColSpanned) {
+ for (var prevColIdx = aColIdx - 1; prevColIdx >= 0; prevColIdx--) {
+ var prevCellState = aCellsArray[aRowIdx][prevColIdx];
+ if (!(prevCellState & kColSpanned)) {
+ origColIdx = prevColIdx;
+ break;
+ }
+ }
+ }
+
+ return [origRowIdx, origColIdx];
+}
diff --git a/accessible/tests/mochitest/table/a11y.ini b/accessible/tests/mochitest/table/a11y.ini
new file mode 100644
index 000000000..ba8e6ba98
--- /dev/null
+++ b/accessible/tests/mochitest/table/a11y.ini
@@ -0,0 +1,27 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+
+[test_css_tables.html]
+[test_headers_ariagrid.html]
+[test_headers_ariatable.html]
+[test_headers_listbox.xul]
+[test_headers_table.html]
+[test_headers_tree.xul]
+[test_indexes_ariagrid.html]
+[test_indexes_listbox.xul]
+[test_indexes_table.html]
+[test_indexes_tree.xul]
+[test_layoutguess.html]
+[test_mtable.html]
+[test_sels_ariagrid.html]
+[test_sels_listbox.xul]
+[test_sels_table.html]
+[test_sels_tree.xul]
+[test_struct_ariagrid.html]
+[test_struct_ariatreegrid.html]
+[test_struct_listbox.xul]
+[test_struct_table.html]
+[test_struct_tree.xul]
+[test_table_1.html]
+[test_table_2.html]
diff --git a/accessible/tests/mochitest/table/test_css_tables.html b/accessible/tests/mochitest/table/test_css_tables.html
new file mode 100644
index 000000000..f93b0770a
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_css_tables.html
@@ -0,0 +1,116 @@
+<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+ <title>CSS display:table is not a table</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // elements with display:table
+
+ // only display:table
+ var accTree =
+ { SECTION: [
+ { TEXT_LEAF: [ ] }
+ ] };
+ testAccessibleTree("table1", accTree);
+
+ // only display:table and display:table-cell
+ accTree =
+ { SECTION: [
+ { SECTION: [
+ { TEXT_LEAF: [ ] }
+ ] }
+ ] };
+ testAccessibleTree("table2", accTree);
+
+ // display:table, display:table-row, and display:table-cell
+ accTree =
+ { SECTION: [
+ { SECTION: [
+ { TEXT_LEAF: [ ] }
+ ] }
+ ] };
+ testAccessibleTree("table3", accTree);
+
+ // display:table, display:table-row-group, display:table-row, and display:table-cell
+ accTree =
+ { SECTION: [
+ { SECTION: [
+ { TEXT_LEAF: [ ] }
+ ] }
+ ] };
+ testAccessibleTree("table4", accTree);
+
+ // display:inline-table
+ accTree =
+ { TEXT_CONTAINER: [
+ { TEXT_CONTAINER: [
+ { TEXT_LEAF: [ ] }
+ ] }
+ ] };
+ testAccessibleTree("table5", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ title=" div with display:table exposes table semantics"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1007975">Mozilla Bug 1007975</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="table1" style="display:table">
+ table1
+ </div>
+
+ <div id="table2" style="display:table">
+ <div style="display:table-cell">table2</div>
+ </div>
+
+ <div id="table3" style="display:table">
+ <div style="display:table-row">
+ <div style="display:table-cell">table3</div>
+ </div>
+ </div>
+
+ <div id="table4" style="display:table">
+ <div style="display:table-row-group">
+ <div style="display:table-row">
+ <div style="display:table-cell">table4</div>
+ </div>
+ </div>
+ </div>
+
+ <div>
+ <span id="table5" style="display:inline-table">
+ <span style="display:table-row">
+ <span style="display:table-cell">table5</div>
+ </span>
+ </span>
+ </div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_headers_ariagrid.html b/accessible/tests/mochitest/table/test_headers_ariagrid.html
new file mode 100644
index 000000000..88abb248a
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_headers_ariagrid.html
@@ -0,0 +1,185 @@
+<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+ <title>Table header information cells for ARIA grid</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // column and row headers from markup
+
+ headerInfoMap = [
+ {
+ cell: "table_dc_1",
+ rowHeaderCells: [ "table_rh_1" ],
+ columnHeaderCells: [ "table_ch_2" ]
+ },
+ {
+ cell: "table_dc_2",
+ rowHeaderCells: [ "table_rh_1" ],
+ columnHeaderCells: [ "table_ch_3" ]
+ },
+ {
+ cell: "table_dc_3",
+ rowHeaderCells: [ "table_rh_2" ],
+ columnHeaderCells: [ "table_ch_2" ]
+ },
+ {
+ cell: "table_dc_4",
+ rowHeaderCells: [ "table_rh_2" ],
+ columnHeaderCells: [ "table_ch_3" ]
+ },
+ {
+ cell: "table_rh_1",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table_ch_1" ]
+ },
+ {
+ cell: "table_rh_2",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table_ch_1" ]
+ }
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // column and row headers from markup for crazy grid.
+
+ headerInfoMap = [
+ {
+ // not focusable cell (ARIAGridCellAccessible is used)
+ cell: "table2_dc_1",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table2_ch_1" ]
+ },
+ {
+ // focusable cell (ARIAGridCellAccessible is used)
+ cell: "table2_dc_2",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table2_ch_2" ]
+ }
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // column and row headers from markup for one more crazy grid.
+
+ headerInfoMap = [
+ {
+ // ARIAGridCellAccessible is used
+ cell: "t3_dc_1",
+ rowHeaderCells: [ "t3_rh_1" ],
+ columnHeaderCells: [ ]
+ },
+ {
+ // ARIAGridCellAccessible is used (inside rowgroup)
+ cell: "t3_dc_2",
+ rowHeaderCells: [ "t3_rh_2" ],
+ columnHeaderCells: [ ]
+ }
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ title="implement IAccessibleTable2"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=512424">Mozilla Bug 512424</a>
+ <a target="_blank"
+ title="nsHTMLTableCellAccessible is used in dojo's crazy ARIA grid"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=513848">Mozilla Bug 513848</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div role="grid">
+ <div role="row">
+ <span id="table_ch_1" role="columnheader">col_1</span>
+ <span id="table_ch_2" role="columnheader">col_2</span>
+ <span id="table_ch_3" role="columnheader">col_3</span>
+ </div>
+ <div role="row">
+ <span id="table_rh_1" role="rowheader">row_1</span>
+ <span id="table_dc_1" role="gridcell">cell1</span>
+ <span id="table_dc_2" role="gridcell">cell2</span>
+ </div>
+ <div role="row">
+ <span id="table_rh_2" role="rowheader">row_2</span>
+ <span id="table_dc_3" role="gridcell">cell3</span>
+ <span id="table_dc_4" role="gridcell">cell4</span>
+ </div>
+ </div>
+
+ <div role="grid">
+ <div role="row">
+ <table role="presentation">
+ <tr>
+ <td id="table2_ch_1" role="columnheader">header1</td>
+ <td id="table2_ch_2" role="columnheader">header2</td>
+ </tr>
+ </table>
+ </div>
+ <div role="row">
+ <table role="presentation">
+ <tr>
+ <td id="table2_dc_1" role="gridcell">cell1</td>
+ <td id="table2_dc_2" role="gridcell" tabindex="-1">cell2</td>
+ </tr>
+ </table>
+ </div>
+ </div>
+
+ <div role="grid">
+ <table role="presentation">
+ <tbody role="presentation">
+ <tr role="row">
+ <th id="t3_rh_1" role="rowheader">Row 1</th>
+ <td id="t3_dc_1" role="gridcell" tabindex="-1">
+ Apple Inc.
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <div role="rowgroup" tabindex="0">
+ <table role="presentation">
+ <tbody role="presentation">
+ <tr role="row">
+ <th id="t3_rh_2" role="rowheader">Row 2</th>
+ <td id="t3_dc_2" role="gridcell" tabindex="-1">
+ Apple-Shmapple Inc.
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_headers_ariatable.html b/accessible/tests/mochitest/table/test_headers_ariatable.html
new file mode 100644
index 000000000..d6d3b1a97
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_headers_ariatable.html
@@ -0,0 +1,96 @@
+<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+ <title>Table header information cells for ARIA table</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // column and row headers from markup
+
+ headerInfoMap = [
+ {
+ cell: "table_dc_1",
+ rowHeaderCells: [ "table_rh_1" ],
+ columnHeaderCells: [ "table_ch_2" ]
+ },
+ {
+ cell: "table_dc_2",
+ rowHeaderCells: [ "table_rh_1" ],
+ columnHeaderCells: [ "table_ch_3" ]
+ },
+ {
+ cell: "table_dc_3",
+ rowHeaderCells: [ "table_rh_2" ],
+ columnHeaderCells: [ "table_ch_2" ]
+ },
+ {
+ cell: "table_dc_4",
+ rowHeaderCells: [ "table_rh_2" ],
+ columnHeaderCells: [ "table_ch_3" ]
+ },
+ {
+ cell: "table_rh_1",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table_ch_1" ]
+ },
+ {
+ cell: "table_rh_2",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table_ch_1" ]
+ }
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ title="support ARIA table and cell roles"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1173364">Bug 1173364</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div role="table">
+ <div role="row">
+ <span id="table_ch_1" role="columnheader">col_1</span>
+ <span id="table_ch_2" role="columnheader">col_2</span>
+ <span id="table_ch_3" role="columnheader">col_3</span>
+ </div>
+ <div role="row">
+ <span id="table_rh_1" role="rowheader">row_1</span>
+ <span id="table_dc_1" role="cell">cell1</span>
+ <span id="table_dc_2" role="cell">cell2</span>
+ </div>
+ <div role="row">
+ <span id="table_rh_2" role="rowheader">row_2</span>
+ <span id="table_dc_3" role="cell">cell3</span>
+ <span id="table_dc_4" role="cell">cell4</span>
+ </div>
+ </div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_headers_listbox.xul b/accessible/tests/mochitest/table/test_headers_listbox.xul
new file mode 100644
index 000000000..343f112db
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_headers_listbox.xul
@@ -0,0 +1,194 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Table header information cells for XUL listbox">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // XUL listbox
+
+ var headerInfoMap = [
+ {
+ cell: "lb1_cell0",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "lb1_header1" ]
+ },
+ {
+ cell: "lb1_cell1",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "lb1_header2" ]
+ },
+ {
+ cell: "lb1_cell2",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "lb1_header3" ]
+ },
+ {
+ cell: "lb1_cell3",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "lb1_header1" ]
+ },
+ {
+ cell: "lb1_cell4",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "lb1_header2" ]
+ },
+ {
+ cell: "lb1_cell5",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "lb1_header3" ]
+ },
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ //////////////////////////////////////////////////////////////////////////
+ // XUL listbox with ARIA
+
+ headerInfoMap = [
+ {
+ cell: "lb2_cell0",
+ rowHeaderCells: [],
+ columnHeaderCells: []
+ },
+ {
+ cell: "lb2_cell1",
+ rowHeaderCells: [],
+ columnHeaderCells: []
+ },
+ {
+ cell: "lb2_cell2",
+ rowHeaderCells: [],
+ columnHeaderCells: []
+ },
+ {
+ cell: "lb2_cell3",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "lb2_cell0" ]
+ },
+ {
+ cell: "lb2_cell4",
+ rowHeaderCells: [ "lb2_cell3" ],
+ columnHeaderCells: [ "lb2_cell1" ]
+ },
+ {
+ cell: "lb2_cell5",
+ rowHeaderCells: [ "lb2_cell3" ],
+ columnHeaderCells: [ "lb2_cell2" ]
+ },
+ {
+ cell: "lb2_cell6",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "lb2_cell0" ]
+ },
+ {
+ cell: "lb2_cell7",
+ rowHeaderCells: [ "lb2_cell6" ],
+ columnHeaderCells: [ "lb2_cell1" ]
+ },
+ {
+ cell: "lb2_cell8",
+ rowHeaderCells: [ "lb2_cell6" ],
+ columnHeaderCells: [ "lb2_cell2" ]
+ }
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=512424"
+ title="implement IAccessibleTable2">
+ Mozilla Bug 512424
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+
+ <label control="listbox" value="multicolumn listbox with header"/>
+ <listbox id="listbox">
+ <listhead>
+ <listheader id="lb1_header1" label="header1"/>
+ <listheader id="lb1_header2" label="header2"/>
+ <listheader id="lb1_header3" label="header3"/>
+ </listhead>
+ <listcols>
+ <listcol flex="1"/>
+ <listcol flex="1"/>
+ <listcol flex="1"/>
+ </listcols>
+ <listitem>
+ <listcell id="lb1_cell0" label="cell0"/>
+ <listcell id="lb1_cell1" label="cell1"/>
+ <listcell id="lb1_cell2" label="cell2"/>
+ </listitem>
+ <listitem>
+ <listcell id="lb1_cell3" label="cell3"/>
+ <listcell id="lb1_cell4" label="cell4"/>
+ <listcell id="lb1_cell5" label="cell5"/>
+ </listitem>
+ <listitem>
+ <listcell id="lb1_cell6" label="cell6"/>
+ <listcell id="lb1_cell7" label="cell7"/>
+ <listcell id="lb1_cell8" label="cell8"/>
+ </listitem>
+ </listbox>
+
+ <label control="listbox2" value="multicolumn listbox with ARIA headers"/>
+ <listbox id="listbox2">
+ <listcols>
+ <listcol flex="1"/>
+ <listcol flex="1"/>
+ <listcol flex="1"/>
+ </listcols>
+ <listitem>
+ <listcell role="columnheader" id="lb2_cell0" label="cell0"/>
+ <listcell role="columnheader" id="lb2_cell1" label="cell1"/>
+ <listcell role="columnheader" id="lb2_cell2" label="cell2"/>
+ </listitem>
+ <listitem>
+ <listcell role="rowheader" id="lb2_cell3" label="cell3"/>
+ <listcell id="lb2_cell4" label="cell4"/>
+ <listcell id="lb2_cell5" label="cell5"/>
+ </listitem>
+ <listitem>
+ <listcell role="rowheader" id="lb2_cell6" label="cell6"/>
+ <listcell id="lb2_cell7" label="cell7"/>
+ <listcell id="lb2_cell8" label="cell8"/>
+ </listitem>
+ </listbox>
+
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/table/test_headers_table.html b/accessible/tests/mochitest/table/test_headers_table.html
new file mode 100644
index 000000000..26691fbfb
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_headers_table.html
@@ -0,0 +1,713 @@
+<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+ <title>Table header information cells for HTML table</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // column header from thead and row header from @scope inside of tfoot
+
+ var headerInfoMap = [
+ {
+ cell: "table1_cell_1",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table1_weekday", "table1_date" ]
+ },
+ {
+ cell: "table1_cell_2",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table1_day", "table1_date" ]
+ },
+ {
+ cell: "table1_cell_3",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table1_qty" ]
+ },
+ {
+ cell: "table1_cell_4",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table1_weekday", "table1_date" ]
+ },
+ {
+ cell: "table1_cell_5",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table1_day", "table1_date" ]
+ },
+ {
+ cell: "table1_cell_6",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table1_qty" ]
+ },
+ {
+ cell: "table1_cell_7",
+ rowHeaderCells: [ "table1_total" ],
+ columnHeaderCells: [ "table1_qty" ]
+ }
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ //////////////////////////////////////////////////////////////////////////
+ // column and row headers from thead and @scope
+
+ headerInfoMap = [
+ {
+ cell: "table2_cell_2",
+ rowHeaderCells: [ "table2_rh_1" ],
+ columnHeaderCells: [ "table2_ch_2" ]
+ },
+ {
+ cell: "table2_cell_3",
+ rowHeaderCells: [ "table2_rh_1" ],
+ columnHeaderCells: [ "table2_ch_3" ]
+ },
+ {
+ cell: "table2_cell_5",
+ rowHeaderCells: [ "table2_rh_2" ],
+ columnHeaderCells: [ "table2_ch_2" ]
+ },
+ {
+ cell: "table2_cell_6",
+ rowHeaderCells: [ "table2_rh_2" ],
+ columnHeaderCells: [ "table2_ch_3" ]
+ },
+ {
+ cell: "table2_rh_1",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table2_ch_1" ]
+ },
+ {
+ cell: "table2_rh_2",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table2_ch_1" ]
+ }
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ //////////////////////////////////////////////////////////////////////////
+ // column headers from @headers
+
+ headerInfoMap = [
+ {
+ cell: "table3_cell_1",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table3_ch_1" ]
+ },
+ {
+ cell: "table3_cell_2",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table3_ch_2" ]
+ }
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ //////////////////////////////////////////////////////////////////////////
+ // table consisted of one column
+
+ headerInfoMap = [
+ {
+ cell: "table4_cell",
+ rowHeaderCells: [],
+ columnHeaderCells: [ "table4_ch" ]
+ }
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ //////////////////////////////////////////////////////////////////////////
+ // table consisted of one row
+
+ headerInfoMap = [
+ {
+ cell: "table5_cell",
+ rowHeaderCells: [ "table5_rh" ],
+ columnHeaderCells: [ ]
+ }
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ //////////////////////////////////////////////////////////////////////////
+ // @headers points to table cells
+
+ headerInfoMap = [
+ {
+ cell: "table6_cell",
+ rowHeaderCells: [ "table6_rh" ],
+ columnHeaderCells: [ "table6_ch" ]
+ }
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ //////////////////////////////////////////////////////////////////////////
+ // @scope="rowgroup" and @scope="row"
+
+ headerInfoMap = [
+ {
+ cell: "t7_r1c1",
+ rowHeaderCells: [ "t7_Mary", "t7_Females" ],
+ columnHeaderCells: [ "t7_1km" ]
+ },
+ {
+ cell: "t7_r1c2",
+ rowHeaderCells: [ "t7_Mary", "t7_Females" ],
+ columnHeaderCells: [ "t7_5km" ]
+ },
+ {
+ cell: "t7_r1c3",
+ rowHeaderCells: [ "t7_Mary", "t7_Females" ],
+ columnHeaderCells: [ "t7_10km" ]
+ },
+ {
+ cell: "t7_r2c1",
+ rowHeaderCells: [ "t7_Betsy", "t7_Females" ],
+ columnHeaderCells: [ "t7_1km" ]
+ },
+ {
+ cell: "t7_r2c2",
+ rowHeaderCells: [ "t7_Betsy", "t7_Females" ],
+ columnHeaderCells: [ "t7_5km" ]
+ },
+ {
+ cell: "t7_r2c3",
+ rowHeaderCells: [ "t7_Betsy", "t7_Females" ],
+ columnHeaderCells: [ "t7_10km" ]
+ },
+ {
+ cell: "t7_r3c1",
+ rowHeaderCells: [ "t7_Matt", "t7_Males" ],
+ columnHeaderCells: [ "t7_1km" ]
+ },
+ {
+ cell: "t7_r3c2",
+ rowHeaderCells: [ "t7_Matt", "t7_Males" ],
+ columnHeaderCells: [ "t7_5km" ]
+ },
+ {
+ cell: "t7_r3c3",
+ rowHeaderCells: [ "t7_Matt", "t7_Males" ],
+ columnHeaderCells: [ "t7_10km" ]
+ },
+ {
+ cell: "t7_r4c1",
+ rowHeaderCells: [ "t7_Todd", "t7_Males" ],
+ columnHeaderCells: [ "t7_1km" ]
+ },
+ {
+ cell: "t7_r4c2",
+ rowHeaderCells: [ "t7_Todd", "t7_Males" ],
+ columnHeaderCells: [ "t7_5km" ]
+ },
+ {
+ cell: "t7_r4c3",
+ rowHeaderCells: [ "t7_Todd", "t7_Males" ],
+ columnHeaderCells: [ "t7_10km" ]
+ }
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ //////////////////////////////////////////////////////////////////////////
+ // @scope="colgroup" and @scope="col"
+
+ headerInfoMap = [
+ {
+ cell: "t8_r1c1",
+ rowHeaderCells: [ "t8_1km" ],
+ columnHeaderCells: [ "t8_Mary", "t8_Females" ]
+ },
+ {
+ cell: "t8_r1c2",
+ rowHeaderCells: [ "t8_1km" ],
+ columnHeaderCells: [ "t8_Betsy", "t8_Females" ]
+ },
+ {
+ cell: "t8_r1c3",
+ rowHeaderCells: [ "t8_1km" ],
+ columnHeaderCells: [ "t8_Matt", "t8_Males" ]
+ },
+ {
+ cell: "t8_r1c4",
+ rowHeaderCells: [ "t8_1km" ],
+ columnHeaderCells: [ "t8_Todd", "t8_Males" ]
+ },
+ {
+ cell: "t8_r2c1",
+ rowHeaderCells: [ "t8_5km" ],
+ columnHeaderCells: [ "t8_Mary", "t8_Females" ]
+ },
+ {
+ cell: "t8_r2c2",
+ rowHeaderCells: [ "t8_5km" ],
+ columnHeaderCells: [ "t8_Betsy", "t8_Females" ]
+ },
+ {
+ cell: "t8_r2c3",
+ rowHeaderCells: [ "t8_5km" ],
+ columnHeaderCells: [ "t8_Matt", "t8_Males" ]
+ },
+ {
+ cell: "t8_r2c4",
+ rowHeaderCells: [ "t8_5km" ],
+ columnHeaderCells: [ "t8_Todd", "t8_Males" ]
+ },
+ {
+ cell: "t8_r3c1",
+ rowHeaderCells: [ "t8_10km" ],
+ columnHeaderCells: [ "t8_Mary", "t8_Females" ]
+ },
+ {
+ cell: "t8_r3c2",
+ rowHeaderCells: [ "t8_10km" ],
+ columnHeaderCells: [ "t8_Betsy", "t8_Females" ]
+ },
+ {
+ cell: "t8_r3c3",
+ rowHeaderCells: [ "t8_10km" ],
+ columnHeaderCells: [ "t8_Matt", "t8_Males" ]
+ },
+ {
+ cell: "t8_r3c4",
+ rowHeaderCells: [ "t8_10km" ],
+ columnHeaderCells: [ "t8_Todd", "t8_Males" ]
+ }
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ //////////////////////////////////////////////////////////////////////////
+ // spanned table header cells (v1), @headers define header order
+
+ headerInfoMap = [
+ {
+ cell: "t9_r1c1",
+ rowHeaderCells: [ "t9_females", "t9_mary" ],
+ columnHeaderCells: [ "t9_1km" ]
+ },
+ {
+ cell: "t9_r1c2",
+ rowHeaderCells: [ "t9_females", "t9_mary" ],
+ columnHeaderCells: [ "t9_5km" ]
+ },
+ {
+ cell: "t9_r1c3",
+ rowHeaderCells: [ "t9_females", "t9_mary" ],
+ columnHeaderCells: [ "t9_10km" ]
+ },
+ {
+ cell: "t9_r2c1",
+ rowHeaderCells: [ "t9_females", "t9_betsy" ],
+ columnHeaderCells: [ "t9_1km" ]
+ },
+ {
+ cell: "t9_r2c2",
+ rowHeaderCells: [ "t9_females", "t9_betsy" ],
+ columnHeaderCells: [ "t9_5km" ]
+ },
+ {
+ cell: "t9_r2c3",
+ rowHeaderCells: [ "t9_females", "t9_betsy" ],
+ columnHeaderCells: [ "t9_10km" ]
+ },
+ {
+ cell: "t9_r3c1",
+ rowHeaderCells: [ "t9_males", "t9_matt" ],
+ columnHeaderCells: [ "t9_1km" ]
+ },
+ {
+ cell: "t9_r3c2",
+ rowHeaderCells: [ "t9_males", "t9_matt" ],
+ columnHeaderCells: [ "t9_5km" ]
+ },
+ {
+ cell: "t9_r3c3",
+ rowHeaderCells: [ "t9_males", "t9_matt" ],
+ columnHeaderCells: [ "t9_10km" ]
+ },
+ {
+ cell: "t9_r4c1",
+ rowHeaderCells: [ "t9_males", "t9_todd" ],
+ columnHeaderCells: [ "t9_1km" ]
+ },
+ {
+ cell: "t9_r4c2",
+ rowHeaderCells: [ "t9_males", "t9_todd" ],
+ columnHeaderCells: [ "t9_5km" ]
+ },
+ {
+ cell: "t9_r4c3",
+ rowHeaderCells: [ "t9_males", "t9_todd" ],
+ columnHeaderCells: [ "t9_10km" ]
+ }
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ //////////////////////////////////////////////////////////////////////////
+ // spanned table header cells (v2), @headers define header order
+
+ headerInfoMap = [
+ {
+ cell: "t10_r1c1",
+ rowHeaderCells: [ "t10_1km" ],
+ columnHeaderCells: [ "t10_females", "t10_mary" ]
+ },
+ {
+ cell: "t10_r1c2",
+ rowHeaderCells: [ "t10_1km" ],
+ columnHeaderCells: [ "t10_females", "t10_betsy" ]
+ },
+ {
+ cell: "t10_r1c3",
+ rowHeaderCells: [ "t10_1km" ],
+ columnHeaderCells: [ "t10_males", "t10_matt" ]
+ },
+ {
+ cell: "t10_r1c4",
+ rowHeaderCells: [ "t10_1km" ],
+ columnHeaderCells: [ "t10_males", "t10_todd" ]
+ },
+ {
+ cell: "t10_r2c1",
+ rowHeaderCells: [ "t10_5km" ],
+ columnHeaderCells: [ "t10_females", "t10_mary" ]
+ },
+ {
+ cell: "t10_r2c2",
+ rowHeaderCells: [ "t10_5km" ],
+ columnHeaderCells: [ "t10_females", "t10_betsy" ]
+ },
+ {
+ cell: "t10_r2c3",
+ rowHeaderCells: [ "t10_5km" ],
+ columnHeaderCells: [ "t10_males", "t10_matt" ]
+ },
+ {
+ cell: "t10_r2c4",
+ rowHeaderCells: [ "t10_5km" ],
+ columnHeaderCells: [ "t10_males", "t10_todd" ]
+ },
+ {
+ cell: "t10_r3c1",
+ rowHeaderCells: [ "t10_10km" ],
+ columnHeaderCells: [ "t10_females", "t10_mary" ]
+ },
+ {
+ cell: "t10_r3c2",
+ rowHeaderCells: [ "t10_10km" ],
+ columnHeaderCells: [ "t10_females", "t10_betsy" ]
+ },
+ {
+ cell: "t10_r3c3",
+ rowHeaderCells: [ "t10_10km" ],
+ columnHeaderCells: [ "t10_males", "t10_matt" ]
+ },
+ {
+ cell: "t10_r3c4",
+ rowHeaderCells: [ "t10_10km" ],
+ columnHeaderCells: [ "t10_males", "t10_todd" ]
+ }
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ title="implement IAccessibleTable2"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=512424">
+ Bug 512424
+ </a>
+ <a target="_blank"
+ title="Table headers not associated when header is a td element with no scope"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=704465">
+ Bug 704465
+ </a>
+ <a target="_blank"
+ title="Support rowgroup and colgroup HTML scope"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1141978">
+ Bug 1141978
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <table id="table1" border="1">
+ <thead>
+ <tr>
+ <th id="table1_date" colspan="2">Date</th>
+ <th id="table1_qty" rowspan="2">Qty</th>
+ </tr>
+ <tr>
+ <th id="table1_weekday">Weekday</th>
+ <th id="table1_day">Day</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td id="table1_cell_1">Mon</td>
+ <td id="table1_cell_2">1</td>
+ <td id="table1_cell_3">20</td>
+ </tr>
+ <tr>
+ <td id="table1_cell_4">Thu</td>
+ <td id="table1_cell_5">2</td>
+ <td id="table1_cell_6">15</td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr>
+ <th id="table1_total" scope="row" colspan="2">Total</th>
+ <td id="table1_cell_7">35</td>
+ </tr>
+ </tfoot>
+ </table>
+
+ <table id="table2" border="1">
+ <thead>
+ <tr>
+ <th id="table2_ch_1">col1</th>
+ <th id="table2_ch_2">col2</th>
+ <td id="table2_ch_3" scope="col">col3</td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <th id="table2_rh_1">row1</th>
+ <td id="table2_cell_2">cell1</td>
+ <td id="table2_cell_3">cell2</td>
+ </tr>
+ <tr>
+ <td id="table2_rh_2" scope="row">row2</td>
+ <td id="table2_cell_5">cell3</td>
+ <td id="table2_cell_6">cell4</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table id="table3" border="1">
+ <tr>
+ <td id="table3_cell_1" headers="table3_ch_1">cell1</td>
+ <td id="table3_cell_2" headers="table3_ch_2">cell2</td>
+ </tr>
+ <tr>
+ <td id="table3_ch_1" scope="col">col1</td>
+ <td id="table3_ch_2" scope="col">col2</td>
+ </tr>
+ </table>
+
+ <table id="table4">
+ <thead>
+ <tr>
+ <th id="table4_ch">colheader</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td id="table4_cell">bla</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table id="table5">
+ <tr>
+ <th id="table5_rh">rowheader</th>
+ <td id="table5_cell">cell</td>
+ </tr>
+ </table>
+
+ <table id="table6">
+ <tr>
+ <td>empty cell</th>
+ <td id="table6_ch">colheader</td>
+ </tr>
+ <tr>
+ <td id="table6_rh">rowheader</th>
+ <td id="table6_cell" headers="table6_ch table6_rh">cell</td>
+ </tr>
+ </table>
+
+ <table id="table7" class="data complex" border="1">
+ <caption>Version 1 with rowgroup</caption>
+ <thead>
+ <tr>
+ <td colspan="2">&nbsp;</td>
+ <th id="t7_1km" scope="col">1 km</th>
+ <th id="t7_5km" scope="col">5 km</th>
+ <th id="t7_10km" scope="col">10 km</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <th id="t7_Females" rowspan="2" scope="rowgroup">Females</th>
+ <th id="t7_Mary" scope="row">Mary</th>
+ <td id="t7_r1c1">8:32</td>
+ <td id="t7_r1c2">28:04</td>
+ <td id="t7_r1c3">1:01:16</td>
+ </tr>
+ <tr>
+ <th id="t7_Betsy" scope="row">Betsy</th>
+ <td id="t7_r2c1">7:43</td>
+ <td id="t7_r2c2">26:47</td>
+ <td id="t7_r2c3">55:38</td>
+ </tr>
+ <tr>
+ <th id="t7_Males" rowspan="2" scope="rowgroup">Males</th>
+ <th id="t7_Matt" scope="row">Matt</th>
+ <td id="t7_r3c1">7:55</td>
+ <td id="t7_r3c2">27:29</td>
+ <td id="t7_r3c3">57:04</td>
+ </tr>
+ <tr>
+ <th id="t7_Todd" scope="row">Todd</th>
+ <td id="t7_r4c1">7:01</td>
+ <td id="t7_r4c2">24:21</td>
+ <td id="t7_r4c3">50:35</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table id="table8" class="data complex" border="1">
+ <caption>Version 2 with colgroup</caption>
+ <thead>
+ <tr>
+ <td rowspan="2">&nbsp;</td>
+ <th id="t8_Females" colspan="2" scope="colgroup">Females</th>
+ <th id="t8_Males" colspan="2" scope="colgroup">Males</th>
+ </tr>
+ <tr>
+ <th id="t8_Mary" scope="col">Mary</th>
+ <th id="t8_Betsy" scope="col">Betsy</th>
+ <th id="t8_Matt" scope="col">Matt</th>
+ <th id="t8_Todd" scope="col">Todd</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <th id="t8_1km" scope="row">1 km</th>
+ <td id="t8_r1c1">8:32</td>
+ <td id="t8_r1c2">7:43</td>
+ <td id="t8_r1c3">7:55</td>
+ <td id="t8_r1c4">7:01</td>
+ </tr>
+ <tr>
+ <th id="t8_5km" scope="row">5 km</th>
+ <td id="t8_r2c1">28:04</td>
+ <td id="t8_r2c2">26:47</td>
+ <td id="t8_r2c3">27:27</td>
+ <td id="t8_r2c4">24:21</td>
+ </tr>
+ <tr>
+ <th id="t8_10km" scope="row">10 km</th>
+ <td id="t8_r3c1">1:01:16</td>
+ <td id="t8_r3c2">55:38</td>
+ <td id="t8_r3c3">57:04</td>
+ <td id="t8_r3c4">50:35</td>
+ </tr>
+
+ </tbody>
+ </table>
+
+ <table id="table9" border="1">
+ <caption>
+ Example 1 (row group headers):
+ </caption>
+ <tr>
+ <td colspan="2"><span class="offscreen">empty</span></td>
+ <th id="t9_1km" width="40">1 km</th>
+ <th id="t9_5km" width="35">5 km</th>
+ <th id="t9_10km" width="42">10 km</th>
+ </tr>
+ <tr>
+ <th id="t9_females" width="56" rowspan="2">Females</th>
+ <th id="t9_mary" width="39">Mary</th>
+ <td id="t9_r1c1" headers="t9_females t9_mary t9_1km">8:32</td>
+ <td id="t9_r1c2" headers="t9_females t9_mary t9_5km">28:04</td>
+ <td id="t9_r1c3" headers="t9_females t9_mary t9_10km">1:01:16</td>
+ </tr>
+ <tr>
+ <th id="t9_betsy">Betsy</th>
+ <td id="t9_r2c1" headers="t9_females t9_betsy t9_1km">7:43</td>
+ <td id="t9_r2c2" headers="t9_females t9_betsy t9_5km">26:47</td>
+ <td id="t9_r2c3" headers="t9_females t9_betsy t9_10km">55:38</td>
+ </tr>
+ <tr>
+ <th id="t9_males" rowspan="2">Males</th>
+ <th id="t9_matt">Matt</th>
+ <td id="t9_r3c1" headers="t9_males t9_matt t9_1km">7:55</td>
+ <td id="t9_r3c2" headers="t9_males t9_matt t9_5km">27:29</td>
+ <td id="t9_r3c3" headers="t9_males t9_matt t9_10km">57:04</td>
+ </tr>
+ <tr>
+ <th id="t9_todd">Todd</th>
+ <td id="t9_r4c1" headers="t9_males t9_todd t9_1km">7:01</td>
+ <td id="t9_r4c2" headers="t9_males t9_todd t9_5km">24:21</td>
+ <td id="t9_r4c3" headers="t9_males t9_todd t9_10km">50:35</td>
+ </tr>
+ </table>
+
+ <table id="table10" border="1">
+ <caption>
+ Example 2 (column group headers):
+ </caption>
+ <tr>
+ <td rowspan="2"><span class="offscreen">empty</span></td>
+ <th colspan="2" id="t10_females">Females</th>
+ <th colspan="2" id="t10_males">Males</th>
+ </tr>
+ <tr>
+ <th width="40" id="t10_mary">Mary</th>
+ <th width="35" id="t10_betsy">Betsy</th>
+ <th width="42" id="t10_matt">Matt</th>
+ <th width="42" id="t10_todd">Todd</th>
+ </tr>
+ <tr>
+ <th width="39" id="t10_1km">1 km</th>
+ <td headers="t10_females t10_mary t10_1km" id="t10_r1c1">8:32</td>
+ <td headers="t10_females t10_betsy t10_1km" id="t10_r1c2">7:43</td>
+ <td headers="t10_males t10_matt t10_1km" id="t10_r1c3">7:55</td>
+ <td headers="t10_males t10_todd t10_1km" id="t10_r1c4">7:01</td>
+ </tr>
+ <tr>
+ <th id="t10_5km">5 km</th>
+ <td headers="t10_females t10_mary t10_5km" id="t10_r2c1">28:04</td>
+ <td headers="t10_females t10_betsy t10_5km" id="t10_r2c2">26:47</td>
+ <td headers="t10_males t10_matt t10_5km" id="t10_r2c3">27:29</td>
+ <td headers="t10_males t10_todd t10_5km" id="t10_r2c4">24:21</td>
+ </tr>
+ <tr>
+ <th id="t10_10km">10 km</th>
+ <td headers="t10_females t10_mary t10_10km" id="t10_r3c1">1:01:16</td>
+ <td headers="t10_females t10_betsy t10_10km" id="t10_r3c2">55:38</td>
+ <td headers="t10_males t10_matt t10_10km" id="t10_r3c3">57:04</td>
+ <td headers="t10_males t10_todd t10_10km" id="t10_r3c4">50:35</td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_headers_tree.xul b/accessible/tests/mochitest/table/test_headers_tree.xul
new file mode 100644
index 000000000..9b69e8a0d
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_headers_tree.xul
@@ -0,0 +1,101 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Table header information cells for XUL tree">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../table.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "debug";
+
+ function doTest()
+ {
+ var treeAcc = getAccessible("tree", [nsIAccessibleTable]);
+
+ var headerInfoMap = [
+ {
+ cell: treeAcc.getCellAt(0, 0),
+ rowHeaderCells: [],
+ columnHeaderCells: [ "col" ]
+ },
+ {
+ cell: treeAcc.getCellAt(0, 1),
+ rowHeaderCells: [],
+ columnHeaderCells: [ "scol" ]
+ },
+ {
+ cell: treeAcc.getCellAt(1, 0),
+ rowHeaderCells: [],
+ columnHeaderCells: [ "col" ]
+ },
+ {
+ cell: treeAcc.getCellAt(1, 1),
+ rowHeaderCells: [],
+ columnHeaderCells: [ "scol" ]
+ },
+ {
+ cell: treeAcc.getCellAt(2, 0),
+ rowHeaderCells: [],
+ columnHeaderCells: [ "col" ]
+ },
+ {
+ cell: treeAcc.getCellAt(2, 1),
+ rowHeaderCells: [],
+ columnHeaderCells: [ "scol" ]
+ },
+ ];
+
+ testHeaderCells(headerInfoMap);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yXULTreeLoadEvent(doTest, "tree", new nsTableTreeView(3));
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=512424"
+ title="implement IAccessibleTable2">
+ Mozilla Bug 512424
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="debug"/>
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ <treecol id="scol" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren id="treechildren"/>
+ </tree>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/table/test_indexes_ariagrid.html b/accessible/tests/mochitest/table/test_indexes_ariagrid.html
new file mode 100644
index 000000000..62ef0ed9d
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_indexes_ariagrid.html
@@ -0,0 +1,139 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Table indexes for ARIA grid tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA grid
+ var idxes = [
+ [0, 1, 2],
+ [3, 4, 5],
+ [6, 7, 8],
+ [9, 10, 11]
+ ];
+ testTableIndexes("grid", idxes);
+
+ idxes = [
+ [0, 1, 2],
+ [3, 4, 5],
+ [6, 7, 8],
+ [9, 10, 11]
+ ];
+ testTableIndexes("grid-rowgroups", idxes);
+
+ //////////////////////////////////////////////////////////////////////////
+ // a bit crazy ARIA grid
+ idxes = [
+ [0, 1],
+ [2, 3]
+ ];
+ testTableIndexes("grid2", idxes);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=386813"
+ title="support nsIAccessibleTable on ARIA grid/treegrid">Mozilla Bug 386813</a>
+ <a target="_blank"
+ title="nsHTMLTableCellAccessible is used in dojo's crazy ARIA grid"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=513848">Mozilla Bug 513848</a>
+ <a target="_blank"
+ title="ARIA grid with rowgroup breaks table row/col counting and indices"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=761853">Mozilla Bug 761853</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div role="grid" id="grid">
+ <div role="row">
+ <span role="columnheader">column1</span>
+ <span role="columnheader">column2</span>
+ <span role="columnheader">column3</span>
+ </div>
+ <div role="row">
+ <span role="rowheader">row1</span>
+ <span role="gridcell">cell1</span>
+ <span role="gridcell">cell2</span>
+ </div>
+ <div role="row">
+ <span role="rowheader">row2</span>
+ <span role="gridcell">cell3</span>
+ <span role="gridcell">cell4</span>
+ </div>
+ <div role="row">
+ <span role="rowheader">row3</span>
+ <span role="gridcell">cell5</span>
+ <span role="gridcell">cell6</span>
+ </div>
+ </div>
+
+ <div role="grid" id="grid-rowgroups">
+ <div role="row">
+ <span role="columnheader">grid-rowgroups-col1</span>
+ <span role="columnheader">grid-rowgroups-col2</span>
+ <span role="columnheader">grid-rowgroups-col3</span>
+ </div>
+ <div role="rowgroup">
+ <div role="row">
+ <span role="rowheader">grid-rowgroups-row1</span>
+ <span role="gridcell">grid-rowgroups-cell1</span>
+ <span role="gridcell">grid-rowgroups-cell2</span>
+ </div>
+ <div role="row">
+ <span role="rowheader">grid-rowgroups-row2</span>
+ <span role="gridcell">grid-rowgroups-cell3</span>
+ <span role="gridcell">grid-rowgroups-cell4</span>
+ </div>
+ </div>
+ <div role="row">
+ <span role="rowheader">grid-rowgroups-row3</span>
+ <span role="gridcell">grid-rowgroups-cell5</span>
+ <span role="gridcell">grid-rowgroups-cell6</span>
+ </div>
+ </div>
+
+ <div role="grid" id="grid2">
+ <div role="row">
+ <table role="presentation">
+ <tr>
+ <td role="columnheader">header1</td>
+ <td role="columnheader">header2</td>
+ </tr>
+ </table>
+ </div>
+ <div role="row">
+ <table role="presentation">
+ <tr>
+ <td role="gridcell">cell1</td>
+ <td role="gridcell" tabindex="-1">cell2</td>
+ </tr>
+ </table>
+ </div>
+ </div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_indexes_listbox.xul b/accessible/tests/mochitest/table/test_indexes_listbox.xul
new file mode 100644
index 000000000..997d687f4
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_indexes_listbox.xul
@@ -0,0 +1,85 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Table indices of accessible table for XUL listbox">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ function doTest()
+ {
+ var idxes = [
+ [0, 1, 2],
+ [3, 4, 5],
+ [6, 7, 8]
+ ];
+ testTableIndexes("listbox", idxes);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=512424"
+ title="implement IAccessibleTable2">
+ Mozilla Bug 512424
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+
+ <label control="listbox" value="multicolumn listbox with header"/>
+ <listbox id="listbox">
+ <listhead>
+ <listheader label="header1"/>
+ <listheader label="header2"/>
+ <listheader label="header3"/>
+ </listhead>
+ <listcols>
+ <listcol flex="1"/>
+ <listcol flex="1"/>
+ <listcol flex="1"/>
+ </listcols>
+ <listitem>
+ <listcell label="cell0"/>
+ <listcell label="cell1"/>
+ <listcell label="cell2"/>
+ </listitem>
+ <listitem>
+ <listcell label="cell3"/>
+ <listcell label="cell4"/>
+ <listcell label="cell5"/>
+ </listitem>
+ <listitem>
+ <listcell label="cell6"/>
+ <listcell label="cell7"/>
+ <listcell label="cell8"/>
+ </listitem>
+ </listbox>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/table/test_indexes_table.html b/accessible/tests/mochitest/table/test_indexes_table.html
new file mode 100644
index 000000000..356a55933
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_indexes_table.html
@@ -0,0 +1,410 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=410052
+-->
+<head>
+ <title>Table indexes chrome tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // table
+ var idxes = [
+ [0, 1, 2],
+ [3, 4, 5],
+ [6, 7, 7],
+ [6, 8, 9]
+ ];
+
+ testTableIndexes("table", idxes);
+
+ //////////////////////////////////////////////////////////////////////////
+ // tableborder
+ idxes = [
+ [0, 1, 2],
+ [3, 4, 5],
+ [6, 7, 7],
+ [6, 8, 9]
+ ];
+
+ testTableIndexes("tableborder", idxes);
+
+ //////////////////////////////////////////////////////////////////////////
+ // table
+ var idxes = [
+ [ 0, 1, 2, 2, 3, 4, 5, 6],
+ [ 7, 8, 9, 10, 11, 12, 13, 6],
+ [14, 15, 15, 16, 17, 18, 19, 6],
+ [20, 15, 15, 21, 22, 18, 23, 6]
+ ];
+
+ testTableIndexes("table2", idxes);
+
+ //////////////////////////////////////////////////////////////////////////
+ // tableinsane1 (empty row groups)
+ idxes = [
+ [0, 1, 2],
+ [3, 4, 5],
+ [6, 7, 7],
+ [6, 8, 9]
+ ];
+
+ testTableIndexes("tableinsane1", idxes);
+
+ //////////////////////////////////////////////////////////////////////////
+ // tableinsane2 (empry rows)
+ idxes = [
+ [-1, -1, -1],
+ [-1, -1, -1],
+ [ 0, 1, 2]
+ ];
+
+ testTableIndexes("tableinsane2", idxes);
+
+ //////////////////////////////////////////////////////////////////////////
+ // tableinsane3 (cell holes)
+ idxes = [
+ [0, 1, -1],
+ [2, 3, 4]
+ ];
+
+ testTableIndexes("tableinsane3", idxes);
+
+ //////////////////////////////////////////////////////////////////////////
+ // tableinsane3.2 (cell holes, row spans, fixed in bug 417912)
+ idxes = [
+ [0, 1, 2],
+ [3, -1, 2],
+ [4, 5, 2]
+ ];
+
+ testTableIndexes("tableinsane3.2", idxes);
+
+ //////////////////////////////////////////////////////////////////////////
+ // tableinsane4 (empty row groups/rows and cell holes)
+ idxes = [
+ [ 0, 1, 2],
+ [-1, -1, -1],
+ [ 3, 4, 5],
+ [ 6, 6, 7],
+ [ 8, -1, 7],
+ [ 9, 9, 9]
+ ];
+ testTableIndexes("tableinsane4", idxes);
+
+ //////////////////////////////////////////////////////////////////////////
+ // tableinsane5 (just a crazy table)
+ idxes = [
+ [ 0, 1, 2, -1, -1],
+ [-1, -1, -1, -1, -1],
+ [ 3, 4, 5, -1, -1],
+ [ 6, 7, -1, -1, -1],
+ [ 6, 8, 9, -1, -1],
+ [ 6, 10, 9, 11, 12]
+ ];
+ testTableIndexes("tableinsane5", idxes);
+
+ //////////////////////////////////////////////////////////////////////////
+ // tableinsane6 (overlapping cells, mad table)
+ idxes = [
+ [ 0, 1, 2, -1, -1],
+ [-1, -1, -1, -1, -1],
+ [ 3, 4, 5, -1, -1],
+ [ 6, 6, 7, -1, -1],
+ [ 8, 9, 7, -1, -1],
+ [ 10, 9, 7, 11, 12]
+ ];
+ testTableIndexes("tableinsane6", idxes);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="GetIndexAt and GetRowAtIndex and GetColumnAtIndex on HTML tables are inconsistent"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=410052">
+ Bug 410052
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!--
+ If you change the structure of the table please make sure to change
+ the indexes count in 'for' statement in the script above.
+ -->
+ <table border="1" id="table">
+ <caption><strong><b><font size="29">this is a caption for this table</font></b></strong></caption>
+ <thead>
+ <tr>
+ <th>col1</th>
+ <th>col2</th>
+ <th>col3</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td rowspan="0">4</td>
+ <td colspan="2">5</td>
+ </tr>
+ <tr>
+ <td>6</td>
+ <td>7</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table border="1" id="tableborder" style="border-collapse:collapse">
+ <caption><strong><b><font size="29">this is a caption for this bc table</font></b></strong></caption>
+ <thead>
+ <tr>
+ <th>col1</th>
+ <th>col2</th>
+ <th>col3</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td rowspan="2">4</td>
+ <td colspan="2">5</td>
+ </tr>
+ <tr>
+ <td>6</td>
+ <td>7</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table cellpadding="2" cellspacing="2" border="1" width="50%" id="table2">
+ <caption>column and row spans</caption>
+ <tbody>
+ <tr>
+ <td>0</td>
+ <td>1</td>
+ <td rowspan="1" colspan="2">2</td>
+ <td>3</td>
+ <td>4</td>
+ <td>5</td>
+ <td rowspan="4" colspan="1">6</td>
+ </tr>
+ <tr>
+ <td>7</td>
+ <td>8</td>
+ <td>8</td>
+ <td>10</td>
+ <td>11</td>
+ <td>12</td>
+ <td>13</td>
+ </tr>
+ <tr>
+ <td>14</td>
+ <td rowspan="2" colspan="2">15</td>
+ <td>16</td>
+ <td>17</td>
+ <td rowspan="2" colspan="1">18</td>
+ <td>19</td>
+ </tr>
+ <tr>
+ <td>20</td>
+ <td>21</td>
+ <td>22</td>
+ <td>23</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table border="1" id="tableinsane1">
+ <caption>test empty row groups</caption>
+ <thead>
+ <tr>
+ <th>col1</th>
+ <th>col2</th>
+ <th>col3</th>
+ </tr>
+ </thead>
+ <tbody></tbody>
+ <tbody></tbody>
+ <tbody></tbody>
+ <tbody>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td rowspan="2">4</td>
+ <td colspan="2">5</td>
+ </tr>
+ <tr>
+ <td>6</td>
+ <td>7</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table border="1" id="tableinsane2">
+ <caption>empty rows</caption>
+ <tbody><tr></tr><tr></tr></tbody>
+ <tbody></tbody>
+ <tbody>
+ <tr>
+ <td>0</td>
+ <td>1</td>
+ <td>2</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table border="1" id="tableinsane3">
+ <caption>missed cell</caption>
+ <tbody>
+ <tr>
+ <td>0</td>
+ <td>1</td>
+ </tr>
+ </tbody>
+ <tbody>
+ <tr>
+ <td>2</td>
+ <td>3</td>
+ <td>4</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table cellpadding="2" cellspacing="2" border="1" id="tableinsane3.2">
+ <tr><td>1</td><td>2</td><td rowspan=3>3</td>
+ <tr><td>4</td>
+ <tr><td>5</td><td>6</td>
+ </table>
+
+ <table border="1" id="tableinsane4">
+ <caption>test empty rows + cellmap holes</caption>
+ <thead>
+ <tr>
+ <th>col1</th>
+ <th>col2</th>
+ <th>col3</th>
+ </tr>
+ </thead>
+ <tbody><tr></tr></tbody>
+ <tbody></tbody>
+ <tbody></tbody>
+ <tbody>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td colspan="2">4</td>
+ <td rowspan="2">5</td>
+ </tr>
+ <tr>
+ <td>6</td>
+ </tr>
+ <tr>
+ <td colspan="3">7</td>
+ </tr>
+
+ </tbody>
+ </table>
+
+ <table border="1" id="tableinsane5">
+ <caption>just a crazy table</caption>
+ <thead>
+ <tr>
+ <th>col1</th>
+ <th>col2</th>
+ <th>col3</th>
+ </tr>
+ </thead>
+ <tbody><tr></tr></tbody>
+ <tbody></tbody>
+ <tbody></tbody>
+ <tbody>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td rowspan="0">4</td>
+ <td colspan="0">5</td>
+ </tr>
+ <tr>
+ <td>6</td>
+ <td rowspan="0">7</td>
+ </tr>
+ <tr>
+ <td>8</td>
+ <td>9</td>
+ <td>10</td>
+ </tr>
+
+ </tbody>
+
+ <table border="1" id="tableinsane6" >
+ <caption>overlapping cells</caption>
+ <thead>
+ <tr>
+ <th>header cell 0</th>
+ <th>header cell 1</th>
+ <th>header cell 2</th>
+ </tr>
+ </thead>
+ <tbody><tr></tr></tbody>
+ <tbody></tbody>
+ <tbody></tbody>
+ <tbody>
+ <tr>
+ <td>3</td>
+ <td>4</td>
+ <td>5</td>
+ </tr>
+ <tr>
+ <td colspan="2">6</td>
+ <td rowspan="0">7</td>
+ </tr>
+ <tr>
+ <td>8</td>
+ <td rowspan="0">9</td>
+ </tr>
+ <tr>
+ <td colspan="3">10</td>
+ <td>11</td>
+ <td>12</td>
+ </tr>
+ </tbody>
+ </table>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_indexes_tree.xul b/accessible/tests/mochitest/table/test_indexes_tree.xul
new file mode 100644
index 000000000..89a751419
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_indexes_tree.xul
@@ -0,0 +1,71 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible Table indexes tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../table.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "debug";
+
+ function doTest()
+ {
+ var idxes = [
+ [0, 1],
+ [2, 3],
+ [4, 5]
+ ];
+ testTableIndexes("tree", idxes);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yXULTreeLoadEvent(doTest, "tree", new nsTableTreeView(3));
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727"
+ title="Reorganize implementation of XUL tree accessibility">
+ Mozilla Bug 503727
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="debug"/>
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ <treecol id="scol" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren id="treechildren"/>
+ </tree>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/table/test_layoutguess.html b/accessible/tests/mochitest/table/test_layoutguess.html
new file mode 100644
index 000000000..e35193c85
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_layoutguess.html
@@ -0,0 +1,506 @@
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=495388 -->
+<head>
+ <title>test HTMLTableAccessible::IsProbablyForLayout implementation</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ // Attribute we're looking for
+ var attr = {
+ "layout-guess": "true"
+ };
+
+ // table with role of grid
+ testAbsentAttrs("table1", attr);
+ // table with role of grid and datatable="0"
+ testAbsentAttrs("table1.1", attr);
+
+ // table with landmark role
+ testAbsentAttrs("table2", attr);
+
+ // table with summary
+ testAbsentAttrs("table3", attr);
+
+ // table with caption
+ testAbsentAttrs("table4", attr);
+
+ // layout table with empty caption
+ testAttrs("table4.2", attr, true);
+
+ // table with thead element
+ testAbsentAttrs("table5", attr);
+
+ // table with tfoot element
+ testAbsentAttrs("table5.1", attr);
+
+ // table with colgroup or col elements
+ testAbsentAttrs("table5.2", attr);
+ testAbsentAttrs("table5.3", attr);
+
+ // table with th element
+ testAbsentAttrs("table6", attr);
+
+ // table with headers attribute
+ testAbsentAttrs("table6.2", attr);
+
+ // table with scope attribute
+ testAbsentAttrs("table6.2.2", attr);
+
+ // table with abbr attribute
+ testAbsentAttrs("table6.2.3", attr);
+
+ // table with abbr element
+ testAbsentAttrs("table6.3", attr);
+
+ // table with abbr element having empty text node
+ testAbsentAttrs("table6.4", attr);
+
+ // table with abbr element and non-empty text node
+ testAttrs("table6.5", attr, true);
+
+ // layout table with nested table
+ testAttrs("table9", attr, true);
+
+ // layout table with 1 column
+ testAttrs("table10", attr, true);
+
+ // layout table with 1 row
+ testAttrs("table11", attr, true);
+
+ // table with 5 columns
+ testAbsentAttrs("table12", attr);
+
+ // table with a bordered cell
+ testAbsentAttrs("table13", attr);
+
+ // table with alternating row background colors
+ testAbsentAttrs("table14", attr);
+
+ // table with 3 columns and 21 rows
+ testAbsentAttrs("table15", attr);
+
+ // layout table that has a 100% width
+ testAttrs("table16", attr, true);
+
+ // layout table that has a 95% width in pixels
+ testAttrs("table17", attr, true);
+
+ // layout table with less than 10 columns
+ testAttrs("table18", attr, true);
+
+ // layout table with embedded iframe
+ testAttrs("table19", attr, true);
+
+ // tree grid, no layout table
+ testAbsentAttrs("table20", attr);
+
+ // layout table containing nested data table (having data structures)
+ testAttrs("table21", attr, true);
+ testAttrs("table21.2", attr, true);
+ testAttrs("table21.3", attr, true);
+ testAttrs("table21.4", attr, true);
+ testAttrs("table21.5", attr, true);
+ testAttrs("table21.6", attr, true);
+
+ // layout table having datatable="0" attribute and containing data table structure (tfoot element)
+ testAttrs("table22", attr, true);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=495388"
+ title="Don't treat tables that have a landmark role as layout table">
+ Mozilla Bug 495388
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=690222"
+ title="Data table elements used to determine layout-guess attribute shouldn't be picked from nested tables">
+ Mozilla Bug 690222
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=696975"
+ title="Extend the list of legitimate data table structures">
+ Mozilla Bug 696975
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- Table with role of grid -->
+ <table id="table1" role="grid">
+ <tr>
+ <th>Sender</th>
+ <th>Subject</th>
+ <th>Date</th>
+ </tr>
+ <tr>
+ <td>Marco</td>
+ <td>Test</td>
+ <td>June 12</td>
+ </tr>
+ <tr>
+ <td>David</td>
+ <td>Another test</td>
+ <td>June 12</td>
+ </tr>
+ <tr>
+ <td>Alex</td>
+ <td>Third test</td>
+ <td>June 12</td>
+ </tr>
+ </table>
+ <!-- table with role of grid and datatable="0"-->
+ <table id="table1.1" role="grid" datatable="0">
+ <tr>
+ <td>Cell1</td><td>cell2</td>
+ </tr>
+ </table>
+
+ <!-- table with landmark role -->
+ <table id="table2" role="main">
+ <tr>
+ <th>Sender</th>
+ <th>Subject</th>
+ <th>Date</th>
+ </tr>
+ <tr>
+ <td>Marco</td>
+ <td>Test</td>
+ <td>June 12</td>
+ </tr>
+ <tr>
+ <td>David</td>
+ <td>Another test</td>
+ <td>June 12</td>
+ </tr>
+ <tr>
+ <td>Alex</td>
+ <td>Third test</td>
+ <td>June 12</td>
+ </tr>
+ </table>
+
+ <!-- table with summary -->
+ <table id="table3" summary="This is a table">
+ <tr>
+ <td>Cell1</td><td>cell2</td>
+ </tr>
+ </table>
+
+ <!-- table with caption -->
+ <table id="table4">
+ <caption>This is a table</caption>
+ <tr>
+ <td>Cell1</td><td>cell2</td>
+ </tr>
+ </table>
+
+ <!-- layout table with empty caption -->
+ <table id="table4.2">
+ <caption> </caption>
+ <tr>
+ <td>Cell1</td><td>cell2</td>
+ </tr>
+ </table>
+
+ <!-- table with thead element -->
+ <table id="table5">
+ <thead>
+ <tr>
+ <td>Cell1</td><td>cell2</td>
+ </tr>
+ </thead>
+ </table>
+
+ <!-- table with tfoot element -->
+ <table id="table5.1">
+ <tfoot>
+ <tr>
+ <td>Cell1</td><td>cell2</td>
+ </tr>
+ </tfoot>
+ </table>
+
+ <!-- table with colgroup and col elements -->
+ <table id="table5.2">
+ <colgroup width="20"></colgroup>
+ <tr>
+ <td>Cell1</td><td>cell2</td>
+ </tr>
+ </table>
+ <table id="table5.3">
+ <col width="20">
+ <tr>
+ <td>Cell1</td><td>cell2</td>
+ </tr>
+ </table>
+
+ <!-- table with th element -->
+ <table id="table6">
+ <tr>
+ <th>Cell1</th><th>cell2</th>
+ </tr>
+ </table>
+
+ <!-- table with headers attribute -->
+ <table id="table6.2">
+ <tr>
+ <td headers="a">table6.2 cell</td>
+ </tr>
+ </table>
+
+ <!-- table with scope attribute -->
+ <table id="table6.2.2">
+ <tr>
+ <td scope="a">table6.2.2 cell</td>
+ </tr>
+ </table>
+
+ <!-- table with abbr attribute -->
+ <table id="table6.2.3">
+ <tr>
+ <td abbr="table6.2.3">table6.2.3 cell1</td>
+ </tr>
+ </table>
+
+ <!-- table with abbr element -->
+ <table id="table6.3">
+ <tr>
+ <td>table6.3 cell1</td>
+ <td><abbr>table6.3 cell2</abbr></td>
+ </tr>
+ </table>
+
+ <!-- table with abbr element having empty text node -->
+ <table id="table6.4">
+ <tr>
+ <td>
+ <abbr>abbr</abbr>
+ </td>
+ </tr>
+ </table>
+
+ <!-- table with abbr element and non-empty text node -->
+ <table id="table6.5">
+ <tr>
+ <td>
+ This is a really long text (<abbr>tiarlt</abbr>) inside layout table
+ </td>
+ </tr>
+ </table>
+
+ <!-- layout table with nested table -->
+ <table id="table9">
+ <tr>
+ <td><table><tr><td>Cell</td></tr></table></td>
+ </tr>
+ </table>
+
+ <!-- layout table with 1 column -->
+ <table id="table10">
+ <tr><td>Row1</td></tr>
+ <tr><td>Row2</td></tr>
+ </table>
+
+ <!-- layout table with 1 row and purposely many columns -->
+ <table id="table11">
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td><td>Col4</td><td>Col5</td></tr>
+ </table>
+
+ <!-- table with 5 columns -->
+ <table id="table12">
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td><td>Col4</td><td>Col5</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td><td>Col4</td><td>Col5</td></tr>
+ </table>
+
+ <!-- table with a bordered cell -->
+ <table id="table13" border="1" width="100%" bordercolor="#0000FF">
+ <tr>
+ <td bordercolor="#000000"> </td>
+ <td bordercolor="#000000"> </td>
+ <td bordercolor="#000000"> </td>
+ </tr>
+ <tr>
+ <td bordercolor="#000000"> </td>
+ <td bordercolor="#000000"> </td>
+ <td bordercolor="#000000"> </td>
+ </tr>
+ </table>
+
+ <!-- table with alternating row background colors -->
+ <table id="table14" width="100%">
+ <tr style="background-color: #0000FF;">
+ <td> </td>
+ <td> </td>
+ <td> </td>
+ </tr>
+ <tr style="background-color: #00FF00;">
+ <td> </td>
+ <td> </td>
+ <td> </td>
+ </tr>
+ </table>
+
+ <!-- table with 3 columns and 21 rows -->
+ <table id="table15" border="0">
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ </table>
+
+ <!-- layout table that has a 100% width -->
+ <table id="table16" width="100%">
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ </table>
+
+ <!-- layout table that has a 95% width in pixels -->
+ <table id="table17" width="98%">
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr>
+ </table>
+
+ <!-- layout table with less than 10 columns -->
+ <table id="table18">
+ <tr>
+ <td>Marco</td>
+ <td>Test</td>
+ <td>June 12</td>
+ </tr>
+ <tr>
+ <td>David</td>
+ <td>Another test</td>
+ <td>June 12</td>
+ </tr>
+ <tr>
+ <td>Alex</td>
+ <td>Third test</td>
+ <td>June 12</td>
+ </tr>
+ </table>
+
+ <!-- layout table with embedded iframe -->
+ <table id="table19">
+ <tr><td><iframe id="frame"></iframe></td><td> </td><td> </td></tr>
+ <tr><td> </td><td> </td><td> </td></tr>
+ <tr><td> </td><td> </td><td> </td></tr>
+ <tr><td> </td><td> </td><td> </td></tr>
+ </table>
+
+ <!-- tree grid, no layout table -->
+ <table id="table20" role="treegrid">
+ <tr role="treeitem"><td>Cell1</td><td>Cell2</td></tr>
+ </table>
+
+ <!-- layout table with nested data table containing data table elements -->
+ <table id="table21">
+ <tr>
+ <td>
+ <table>
+ <caption>table</caption>
+ <tr><td>Cell</td></tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ <table id="table21.2">
+ <tr>
+ <td>
+ <table>
+ <colgroup width="20"></colgroup>
+ <tr><th>Cell</th></tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ <table id="table21.3">
+ <tr>
+ <td>
+ <table>
+ <col width="20"></col>
+ <tr><th>Cell</th></tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ <table id="table21.4">
+ <tr>
+ <td>
+ <table>
+ <tr><th>Cell</th></tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ <table id="table21.5">
+ <tr>
+ <td>
+ <table>
+ <thead>
+ <tr><td>Cell</td></tr>
+ </thead>
+ </table>
+ </td>
+ </tr>
+ </table>
+ <table id="table21.6">
+ <tr>
+ <td>
+ <table>
+ <tfoot>
+ <tr><td>Cell</td></tr>
+ </tfoot>
+ </table>
+ </td>
+ </tr>
+ </table>
+
+ <!-- layout table with datatable="0" and tfoot element-->
+ <table id="table22" datatable="0">
+ <tfoot>
+ <tr>
+ <td>Cell1</td><td>cell2</td>
+ </tr>
+ </tfoot>
+ </table>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_mtable.html b/accessible/tests/mochitest/table/test_mtable.html
new file mode 100644
index 000000000..37ea8af50
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_mtable.html
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>MathML table tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ // 'Simple' table
+ var idxes = [
+ [0, 1],
+ [2, 3]
+ ];
+ testTableIndexes("simple", idxes);
+ var cellsArray = [
+ [kDataCell, kDataCell],
+ [kDataCell, kDataCell]
+ ];
+ var rowsArray = [ROLE_MATHML_TABLE_ROW, ROLE_MATHML_TABLE_ROW];
+ testTableStruct("simple", cellsArray, kNoColumnHeader,
+ "", "", kMathTable, rowsArray);
+
+ // 'Complex' table
+ idxes = [
+ [0, 0, 0],
+ [1, 1, 2],
+ [1, 1, 3]
+ ];
+ testTableIndexes("complex", idxes);
+ cellsArray = [
+ [kDataCell, kColSpanned, kColSpanned],
+ [kDataCell, kColSpanned, kDataCell],
+ [kRowSpanned, kSpanned, kDataCell],
+ ];
+ rowsArray = [
+ ROLE_MATHML_TABLE_ROW,
+ ROLE_MATHML_TABLE_ROW,
+ ROLE_MATHML_TABLE_ROW
+ ];
+ testTableStruct("complex", cellsArray, kNoColumnHeader,
+ "", "", kMathTable, rowsArray);
+
+ // 'Simple' table with mlabeledtr
+ // At the moment we do not implement mlabeledtr but just hide the label
+ // with display: none. Thus we just test the role for now. See bug 689641.
+ var idxes = [[0]];
+ testTableIndexes("simple_label", idxes);
+ var cellsArray = [[kDataCell]];
+ rowsArray = [ROLE_MATHML_LABELED_ROW];
+ testTableStruct("simple_label", cellsArray, kNoColumnHeader,
+ "", "", kMathTable, rowsArray);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <math>
+ <mtable id="simple">
+ <mtr>
+ <mtd>
+ <mn>1</mn>
+ </mtd>
+ <mtd>
+ <mn>0</mn>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mn>0</mn>
+ </mtd>
+ <mtd>
+ <mn>1</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+
+ <mtable id="complex">
+ <mtr>
+ <mtd columnspan="3">
+ <mtext>1 x 3</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd rowspan="2" columnspan="2">
+ <mtext>2 x 2</mtext>
+ </mtd>
+ <mtd>
+ <mtext>1 x 1</mtext>
+ </mtd>
+ </mtr>
+ <mtr>
+ <mtd>
+ <mtext>1 x 1</mtext>
+ </mtd>
+ </mtr>
+ </mtable>
+
+ <mtable id="simple_label">
+ <mlabeledtr>
+ <mtd><mtext>1</mtext></mtd>
+ <mtd><mtext>label</mtext></mtd>
+ </mlabeledtr>
+ </mtable>
+ </math>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_sels_ariagrid.html b/accessible/tests/mochitest/table/test_sels_ariagrid.html
new file mode 100644
index 000000000..e8b082471
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_sels_ariagrid.html
@@ -0,0 +1,161 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=410052
+-->
+<head>
+ <title>nsIAccesible selection methods testing for ARIA grid</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA grid
+ var cellsArray =
+ [
+ [ true, true, false, true],
+ [ true, false, true, true],
+ [ true, false, false, true],
+ [ true, true, true, true],
+ [ true, true, true, true]
+ ];
+
+ testTableSelection("table", cellsArray);
+ testUnselectTableColumn("table", 3, cellsArray);
+ testUnselectTableRow("table", 3, cellsArray);
+ testSelectTableColumn("table", 0, cellsArray);
+ testSelectTableRow("table", 0, cellsArray);
+
+ //////////////////////////////////////////////////////////////////////////
+ // a bit crazy ARIA grid
+ cellsArray =
+ [
+ [ false, false],
+ [ false, false]
+ ];
+
+ testTableSelection("grid2", cellsArray);
+ testSelectTableColumn("grid2", 0, cellsArray);
+ testSelectTableRow("grid2", 0, cellsArray);
+ testUnselectTableColumn("grid2", 0, cellsArray);
+ testUnselectTableRow("grid2", 0, cellsArray);
+
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA grid (column and row headers)
+
+ cellsArray =
+ [
+ [ undefined, true, false],
+ [ undefined, true, false]
+ ];
+
+ testTableSelection("grid3", cellsArray);
+ testSelectTableColumn("grid3", 0, cellsArray);
+ testSelectTableRow("grid3", 0, cellsArray);
+ testUnselectTableColumn("grid3", 0, cellsArray);
+ testUnselectTableRow("grid3", 0, cellsArray);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="implement nsIAccessibleTable selection methods for ARIA grids"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=410052">Bug 410052</a>
+ <a target="_blank"
+ title="nsHTMLTableCellAccessible is used in dojo's crazy ARIA grid"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=513848">Bug 513848</a>
+ <a target="_blank"
+ title="ARIA columnheader/rowheader shouldn't be selectable by default"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=888247">Bug 888247</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div role="grid" id="table">
+ <div role="row">
+ <span role="gridcell" aria-selected="true">cell1</span>
+ <span role="gridcell" aria-selected="true">cell2</span>
+ <span role="gridcell">cell3</span>
+ <span role="gridcell" aria-selected="true">cell4</span>
+ </div>
+ <div role="row">
+ <span role="gridcell" aria-selected="true">cell5</span>
+ <span role="gridcell">cell6</span>
+ <span role="gridcell" aria-selected="true">cell7</span>
+ <span role="gridcell" aria-selected="true">cell8</span>
+ </div>
+ <div role="row">
+ <span role="gridcell" aria-selected="true">cell9</span>
+ <span role="gridcell">cell10</span>
+ <span role="gridcell">cell11</span>
+ <span role="gridcell" aria-selected="true">cell12</span>
+ </div>
+ <div role="row" aria-selected="true">
+ <span role="gridcell">cell13</span>
+ <span role="gridcell">cell14</span>
+ <span role="gridcell">cell15</span>
+ <span role="gridcell">cell16</span>
+ </div>
+ <div role="row">
+ <span role="gridcell" aria-selected="true">cell17</span>
+ <span role="gridcell" aria-selected="true">cell18</span>
+ <span role="gridcell" aria-selected="true">cell19</span>
+ <span role="gridcell" aria-selected="true">cell20</span>
+ </div>
+ </div>
+
+ <div role="grid" id="grid2">
+ <div role="row">
+ <table role="presentation">
+ <tr>
+ <td role="columnheader" aria-selected="false">header1</td>
+ <td role="columnheader" aria-selected="false">header2</td>
+ </tr>
+ </table>
+ </div>
+ <div role="row">
+ <table role="presentation">
+ <tr>
+ <td role="gridcell">cell1</td>
+ <td role="gridcell" tabindex="-1">cell2</td>
+ </tr>
+ </table>
+ </div>
+ </div>
+
+ <div role="grid" id="grid3">
+ <div role="row">
+ <div role="columnheader" id="colheader_default">col header1</div>
+ <div role="columnheader" id="colheader_selected" aria-selected="true">col header2</div>
+ <div role="columnheader" id="colheader_notselected" aria-selected="false">col header3</div>
+ </div>
+ <div role="row">
+ <div role="rowheader" id="rowheader_default">row header1</div>
+ <div role="rowheader" id="rowheader_selected" aria-selected="true">row header2</div>
+ <div role="rowheader" id="rowheader_notselected" aria-selected="false">row header3</div>
+ </div>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_sels_listbox.xul b/accessible/tests/mochitest/table/test_sels_listbox.xul
new file mode 100644
index 000000000..83697e8d0
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_sels_listbox.xul
@@ -0,0 +1,247 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="nsIAccessibleTable selection methods on xul:listbox test.">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ function doTest()
+ {
+ var id = "listbox3";
+ var acc = getAccessible(id, [nsIAccessibleTable]);
+
+ var rowCount = acc.rows;
+ var colsCount = acc.columns;
+
+ // columns selection
+ testColumnSelection(id, acc, colsCount, 0, null);
+ acc.selectColumn(0);
+ testColumnSelection(id, acc, colsCount, 0, null);
+
+ // rows selection
+ testRowSelection(id, acc, rowCount, 0, null);
+ acc.selectRow(0);
+ testRowSelection(id, acc, rowCount, 1, [0]);
+ acc.selectRow(1);
+ testRowSelection(id, acc, rowCount, 1, [1]);
+ acc.unselectRow(1);
+ testRowSelection(id, acc, rowCount, 0, null);
+
+ // cells selection
+ testCellSelection(id, acc, rowCount, colsCount, 0, null);
+ acc.selectRow(2);
+ testCellSelection(id, acc, rowCount, colsCount, 3, [6, 7, 8]);
+ acc.unselectRow(2);
+ testCellSelection(id, acc, rowCount, colsCount, 0, null);
+
+ SimpleTest.finish();
+ }
+
+ /**
+ * Helper function to test isColumnSelected(), selectedColumnCount and
+ * getSelectedColumn() methods.
+ */
+ function testColumnSelection(aId, aAcc, aCount, aSelCount, aSelIndexesArray)
+ {
+ // isColumnSelected
+ for (var col = 0; col < aCount; col++) {
+ if (aSelIndexesArray && aSelIndexesArray.indexOf(col) != -1) {
+ is(aAcc.isColumnSelected(col), true,
+ aId + ": column " + col + " should be selected");
+ } else {
+ is(aAcc.isColumnSelected(col), false,
+ aId + ": column " + col + " shouldn't be selected");
+ }
+ }
+
+ // selectedColumnCount
+ is(aAcc.selectedColumnCount, aSelCount,
+ aId + ": wrong number of selected columns");
+
+ // getSelectedColumns
+ var selColsCount = {}, selCols = {};
+ aAcc.getSelectedColumnIndices(selColsCount, selCols);
+
+ is(selColsCount.value, aSelCount,
+ aId + ": wrong number of selected columns");
+
+ if (!aSelIndexesArray) {
+ is(selCols.value, undefined,
+ aId + ": no columns should be selected");
+ } else {
+ for (var i = 0; i < selCols.length; i++) {
+ is(selCols[i], aSelIndexesArray[i],
+ aId + ": wrong selected column index " + i);
+ }
+ }
+ }
+
+ /**
+ * Helper function to test isRowSelected(), selectedRowCount() and
+ * getSelectedRow() methods.
+ */
+ function testRowSelection(aId, aAcc, aCount, aSelCount, aSelIndexesArray)
+ {
+ // isRowSelected
+ for (var row = 0; row < aCount; row++) {
+ if (aSelIndexesArray && aSelIndexesArray.indexOf(row) != -1) {
+ is(aAcc.isRowSelected(row), true,
+ aId + ": row " + row + " should be selected");
+ } else {
+ is(aAcc.isRowSelected(row), false,
+ aId + ": row " + row + " shouldn't be selected");
+ }
+ }
+
+ // selectedRowCount
+ is(aAcc.selectedRowCount, aSelCount,
+ aId + ": wrong number of selected rows");
+
+ // getSelectedRows
+ var selColsCount = {}, selCols = {};
+ aAcc.getSelectedRowIndices(selColsCount, selCols);
+
+ is(selColsCount.value, aSelCount,
+ aId + ": wrong number of selected rows");
+
+ if (!aSelIndexesArray) {
+ is(selCols.value, undefined,
+ aId + ": no row should be selected");
+ } else {
+ for (var i = 0; i < selCols.length; i++) {
+ is(selCols[i], aSelIndexesArray[i],
+ aId + ": wrong selected row index " + i);
+ }
+ }
+ }
+
+ /**
+ * Helper function to test isCellSelected(), selectedCellCount() and
+ * getSelectedCells() methods.
+ */
+ function testCellSelection(aId, aAcc, aRowCount, aColCount,
+ aSelCount, aSelIndexesArray)
+ {
+ // isCellSelected
+ for (var row = 0; row < aRowCount; row++) {
+ for (var col = 0; col < aColCount; col++) {
+ var index = aAcc.getIndexAt(row, col);
+ if (aSelIndexesArray && aSelIndexesArray.indexOf(index) != -1) {
+ is(aAcc.isCellSelected(row, col), true,
+ aId + ": cell (" + row + ", " + col + ") should be selected");
+ } else {
+ is(aAcc.isCellSelected(row, col), false,
+ aId + ": cell (" + row + ", " + col + ") shouldn't be selected");
+ }
+ }
+ }
+
+ // selectedCellCount
+ is(aAcc.selectedCellCount, aSelCount,
+ aId + ": wrong number of selected cells");
+
+ // getSelectedCells
+ var selColsCount = {}, selCols = {};
+ aAcc.getSelectedCellIndices(selColsCount, selCols);
+
+ is(selColsCount.value, aSelCount,
+ aId + ": wrong number of selected cells");
+
+ if (!aSelIndexesArray) {
+ is(selCols.value, undefined,
+ aId + ": no cells should be selected");
+ } else {
+ for (var i = 0; i < selCols.length; i++) {
+ is(selCols[i], aSelIndexesArray[i],
+ aId + ": wrong selected cell index " + i);
+ }
+ }
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=418371"
+ title="implement the rest of methods of nsIAccessibleTable on xul:listbox">
+ Mozilla Bug 418371
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=512424"
+ title="implement IAccessibleTable2">
+ Mozilla Bug 512424
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+
+ <label control="listbox2" value="multicolumn listbox: "/>
+ <listbox id="listbox2">
+ <listcols>
+ <listcol flex="1"/>
+ <listcol flex="1"/>
+ </listcols>
+ <listitem>
+ <listcell label="cell1"/>
+ <listcell label="cell2"/>
+ </listitem>
+ <listitem>
+ <listcell label="cell1"/>
+ <listcell label="cell2"/>
+ </listitem>
+ </listbox>
+
+ <label control="listbox3" value="multicolumn listbox with header"/>
+ <listbox id="listbox3">
+ <listhead>
+ <listheader label="header1"/>
+ <listheader label="header2"/>
+ <listheader label="header3"/>
+ </listhead>
+ <listcols>
+ <listcol flex="1"/>
+ <listcol flex="1"/>
+ <listcol flex="1"/>
+ </listcols>
+ <listitem>
+ <listcell label="cell0"/>
+ <listcell label="cell1"/>
+ <listcell label="cell2"/>
+ </listitem>
+ <listitem>
+ <listcell label="cell3"/>
+ <listcell label="cell4"/>
+ <listcell label="cell5"/>
+ </listitem>
+ <listitem>
+ <listcell label="cell6"/>
+ <listcell label="cell7"/>
+ <listcell label="cell8"/>
+ </listitem>
+ </listbox>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/table/test_sels_table.html b/accessible/tests/mochitest/table/test_sels_table.html
new file mode 100644
index 000000000..c0b37384e
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_sels_table.html
@@ -0,0 +1,180 @@
+<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en">
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <title>nsIAccesible selection methods testing for HTML table</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="text/javascript">
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // table
+
+ var cellsArray =
+ [
+ [false, false, false, kColSpanned, false, false, false, false],
+ [false, false, false, false, false, false, false, kRowSpanned],
+ [false, false, kColSpanned, false, false, false, false, kRowSpanned],
+ [false, kRowSpanned, kSpanned, false, false, kRowSpanned, false, kRowSpanned]
+ ];
+
+ testTableSelection("table", cellsArray);
+
+ var rowCount = 4;
+ for (var rowIdx = 0; rowIdx < rowCount; rowIdx++)
+ testSelectTableRow("table", rowIdx, cellsArray);
+
+ for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ testSelectTableRow("table", rowIdx, cellsArray);
+ testUnselectTableRow("table", rowIdx, cellsArray);
+ }
+
+ var columsCount = 8;
+ for (var colIdx = 0; colIdx < columsCount; colIdx++)
+ testSelectTableColumn("table", colIdx, cellsArray);
+
+ for (var colIdx = 0; colIdx < columsCount; colIdx++) {
+ testSelectTableColumn("table", colIdx, cellsArray);
+ testUnselectTableColumn("table", colIdx, cellsArray);
+ }
+
+ var accTable = getAccessible("table", [nsIAccessibleTable]);
+ ok(!accTable.isProbablyForLayout(), "table is not for layout");
+
+ //////////////////////////////////////////////////////////////////////////
+ // table instane
+
+ cellsArray =
+ [
+ [false, false, false, -1, -1],
+ [false, false, false, -1, -1],
+ [false, false, kColSpanned, kColSpanned, -1],
+ [kRowSpanned, false, false, -1, -1],
+ [kRowSpanned, false, kRowSpanned, false, false]
+ ];
+
+ testTableSelection("tableinsane", cellsArray);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+ </head>
+ <body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=410052"
+ title="Fix our nsHTMLAccessibleTable class so GetIndexAt and GetRowAtIndex and GetColumnAtIndex behave consistently">
+ Mozilla Bug 410052
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=501635"
+ title="nsHTMLTableAccessible::GetSelectedCells contains index duplicates for spanned rows or columns">
+ Mozilla Bug 501635
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=417929"
+ title="nsIAccessiblTable selectRows does not unselect previously selected rows">
+ Mozilla Bug 417929
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=501659"
+ title="HTML table's isRowSelected/isColumnSelected shouldn't fail if row or column has cell holes">
+ Mozilla Bug 501659
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- Test Table -->
+ <br><br><b> Testing Table:</b><br><br>
+ <center>
+ <table cellpadding="2" cellspacing="2" border="1" width="50%" id="table">
+ <tbody>
+ <tr>
+ <td><br></td>
+ <td><br></td>
+ <td rowspan="1" colspan="2"><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td rowspan="4" colspan="1"><br></td>
+ </tr>
+ <tr>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ </tr>
+ <tr>
+ <td><br></td>
+ <td rowspan="2" colspan="2">c1</td>
+ <td><br></td>
+ <td><br></td>
+ <td rowspan="2" colspan="1"><br></td>
+ <td><br></td>
+ </tr>
+ <tr>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table border="1" id="tableinsane">
+ <thead>
+ <tr>
+ <th>col1</th>
+ <th>col2</th>
+ <th>col3</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td rowspan="3">4</td>
+ <td colspan="4">5</td>
+ </tr>
+ <tr>
+ <td>6</td>
+ <td rowspan="2">7</td>
+ </tr>
+ <tr>
+ <td>8</td>
+ <td>9</td>
+ <td>10</td>
+ </tr>
+ </tbody>
+ </table>
+
+ </center>
+ </body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_sels_tree.xul b/accessible/tests/mochitest/table/test_sels_tree.xul
new file mode 100644
index 000000000..ab2d1fc25
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_sels_tree.xul
@@ -0,0 +1,79 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible Table selection tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../table.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "debug";
+
+ function doTest()
+ {
+ var cellsArray =
+ [
+ [false, false],
+ [false, false],
+ [false, false]
+ ];
+
+ testTableSelection("tree", cellsArray);
+ testSelectTableRow("tree", 0, cellsArray);
+ testUnselectTableRow("tree", 0, cellsArray);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yXULTreeLoadEvent(doTest, "tree", new nsTableTreeView(3));
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727"
+ title="Reorganize implementation of XUL tree accessibility">
+ Mozilla Bug 503727
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="debug"/>
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ <treecol id="scol" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren id="treechildren"/>
+ </tree>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/table/test_struct_ariagrid.html b/accessible/tests/mochitest/table/test_struct_ariagrid.html
new file mode 100644
index 000000000..9f771ef3b
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_struct_ariagrid.html
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Table accessible tree and table interface tests for ARIA grid</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // Pure ARIA grid
+ var cellsArray = [
+ [kColHeaderCell, kColHeaderCell, kColHeaderCell],
+ [kRowHeaderCell, kDataCell, kDataCell],
+ [kRowHeaderCell, kDataCell, kDataCell]
+ ];
+
+ testTableStruct("table", cellsArray);
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML table based ARIA grid
+ cellsArray = [
+ [kColHeaderCell, kColHeaderCell, kColHeaderCell],
+ [kDataCell, kDataCell, kDataCell],
+ [kDataCell, kDataCell, kDataCell]
+ ];
+
+ testTableStruct("grid", cellsArray);
+
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA grid with HTML table elements
+ cellsArray = [
+ [kColHeaderCell, kColHeaderCell],
+ [kDataCell, kDataCell]
+ ];
+
+ testTableStruct("grid2", cellsArray);
+
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA grid of wrong markup
+ cellsArray = [ ];
+ testTableStruct("grid3", cellsArray);
+
+ cellsArray = [ [] ];
+ testTableStruct("grid4", cellsArray);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ title="ARIA grid based on HTML table"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=491683">Mozilla Bug 491683</a>
+ <a target="_blank"
+ title="implement IAccessibleTable2"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=512424">Mozilla Bug 512424</a>
+ <a target="_blank"
+ title="nsHTMLTableCellAccessible is used in dojo's crazy ARIA grid"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=513848">Mozilla Bug 513848</a>
+ <a target="_blank"
+ title="Crash [@ AccIterator::GetNext()]"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=675861">Mozilla Bug 675861</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- Not usual markup to avoid text accessible between cell accessibles -->
+ <div id="table" role="grid">
+ <div role="row"><span
+ id="table_ch_1" role="columnheader">col_1</span><span
+ id="table_ch_2" role="columnheader">col_2</span><span
+ id="table_ch_3" role="columnheader">col_3</span></div>
+ <div role="row"><span
+ id="table_rh_1" role="rowheader">row_1</span><span
+ id="table_dc_1" role="gridcell">cell1</span><span
+ id="table_dc_2" role="gridcell">cell2</span></div>
+ <div role="row"><span
+ id="table_rh_2" role="rowheader">row_2</span><span
+ id="table_dc_3" role="gridcell">cell3</span><span
+ id="table_dc_4" role="gridcell">cell4</span></div>
+ </div>
+
+ <table role="grid" id="grid" border="1" cellpadding="10" cellspacing="0">
+ <thead>
+ <tr role="row">
+ <th role="columnheader">subject</td>
+ <th role="columnheader">sender</th>
+ <th role="columnheader">date</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr role="row">
+ <td role="gridcell" tabindex="0">about everything</td>
+ <td role="gridcell">president</td>
+ <td role="gridcell">today</td>
+ </tr>
+ <tr role="row">
+ <td role="gridcell">new bugs</td>
+ <td role="gridcell">mozilla team</td>
+ <td role="gridcell">today</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <!-- ARIA grid containing presentational HTML:table with HTML:td used as ARIA
+ grid cells (focusable and not focusable cells) -->
+ <div role="grid" id="grid2">
+ <div role="row">
+ <table role="presentation">
+ <tr>
+ <td role="columnheader">header1</td>
+ <td role="columnheader">header2</td>
+ </tr>
+ </table>
+ </div>
+ <div role="row">
+ <table role="presentation">
+ <tr>
+ <td role="gridcell">cell1</td>
+ <td role="gridcell" tabindex="-1">cell2</td>
+ </tr>
+ </table>
+ </div>
+ </div>
+
+ <!-- Wrong markup ARIA grid -->
+ <div role="grid" id="grid3"></div>
+ <div role="grid" id="grid4"><div role="row"></div></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_struct_ariatreegrid.html b/accessible/tests/mochitest/table/test_struct_ariatreegrid.html
new file mode 100644
index 000000000..7c97adb9b
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_struct_ariatreegrid.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Table accessible tree and table interface tests for ARIA tree grid</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // HTML based ARIA tree grid
+
+ var cellsArray = [
+ [kColHeaderCell, kColHeaderCell, kColHeaderCell],
+ [kDataCell, kDataCell, kDataCell],
+ [kDataCell, kDataCell, kDataCell]
+ ];
+
+ testTableStruct("treegrid", cellsArray, kNoColumnHeader, "", "",
+ kTreeTable);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ title="ARIA treegrid role on HTML:table makes thead/tbody accessible"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=491683">Mozilla Bug 516133</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <table role="treegrid" id="treegrid"
+ border="1" cellpadding="10" cellspacing="0">
+ <thead>
+ <tr role="row">
+ <th role="columnheader">subject</td>
+ <th role="columnheader">sender</th>
+ <th role="columnheader">date</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr role="row">
+ <td role="gridcell">about everything</td>
+ <td role="gridcell">president</td>
+ <td role="gridcell">today</td>
+ </tr>
+ <tr role="row">
+ <td role="gridcell">new bugs</td>
+ <td role="gridcell">mozilla team</td>
+ <td role="gridcell">today</td>
+ </tr>
+ </tbody>
+ </table>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_struct_listbox.xul b/accessible/tests/mochitest/table/test_struct_listbox.xul
new file mode 100644
index 000000000..102269358
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_struct_listbox.xul
@@ -0,0 +1,117 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Table accessible tree and table interface tests for XUL listboxes">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // Multicolumn listbox.
+
+ var cellsArray = [
+ [kDataCell, kDataCell],
+ [kDataCell, kDataCell]
+ ];
+
+ testTableStruct("listbox1", cellsArray);
+
+ //////////////////////////////////////////////////////////////////////////
+ // Multicolumn listbox with header.
+
+ var cellsArray = [
+ [kDataCell, kDataCell, kDataCell],
+ [kDataCell, kDataCell, kDataCell],
+ [kDataCell, kDataCell, kDataCell]
+ ];
+
+ testTableStruct("listbox2", cellsArray, kListboxColumnHeader);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=512424"
+ title="implement IAccessibleTable2">
+ Mozilla Bug 512424
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+
+ <label control="listbox1" value="multicolumn listbox: "/>
+ <listbox id="listbox1">
+ <listcols>
+ <listcol flex="1"/>
+ <listcol flex="1"/>
+ </listcols>
+ <listitem>
+ <listcell label="cell1"/>
+ <listcell label="cell2"/>
+ </listitem>
+ <listitem>
+ <listcell label="cell1"/>
+ <listcell label="cell2"/>
+ </listitem>
+ </listbox>
+
+ <label control="listbox2" value="multicolumn listbox with header"/>
+ <listbox id="listbox2">
+ <listhead>
+ <listheader label="header1"/>
+ <listheader label="header2"/>
+ <listheader label="header3"/>
+ </listhead>
+ <listcols>
+ <listcol flex="1"/>
+ <listcol flex="1"/>
+ <listcol flex="1"/>
+ </listcols>
+ <listitem>
+ <listcell label="cell0"/>
+ <listcell label="cell1"/>
+ <listcell label="cell2"/>
+ </listitem>
+ <listitem>
+ <listcell label="cell3"/>
+ <listcell label="cell4"/>
+ <listcell label="cell5"/>
+ </listitem>
+ <listitem>
+ <listcell label="cell6"/>
+ <listcell label="cell7"/>
+ <listcell label="cell8"/>
+ </listitem>
+ </listbox>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/table/test_struct_table.html b/accessible/tests/mochitest/table/test_struct_table.html
new file mode 100644
index 000000000..98b954935
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_struct_table.html
@@ -0,0 +1,203 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Table accessible tree and table interface tests for HTML tables</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../table.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // column headers from thead and tfoot
+
+ cellsArray = [
+ [kColHeaderCell, kColHeaderCell, kColSpanned],
+ [kRowSpanned, kColHeaderCell, kColHeaderCell],
+ [kDataCell, kDataCell, kDataCell],
+ [kColHeaderCell, kColHeaderCell, kColHeaderCell]
+ ];
+
+ testTableStruct("table1", cellsArray);
+
+ //////////////////////////////////////////////////////////////////////////
+ // row and column headers from thead and @scope
+
+ var cellsArray = [
+ [kColHeaderCell, kColHeaderCell, kColHeaderCell],
+ [kRowHeaderCell, kDataCell, kDataCell],
+ [kRowHeaderCell, kDataCell, kDataCell]
+ ];
+
+ testTableStruct("table2", cellsArray);
+
+ //////////////////////////////////////////////////////////////////////////
+ // caption and @summary
+
+ cellsArray = [
+ [kColHeaderCell, kColHeaderCell, kColHeaderCell, kColHeaderCell],
+ [kRowHeaderCell, kDataCell, kDataCell, kDataCell],
+ [kRowHeaderCell, kDataCell, kDataCell, kDataCell]
+ ];
+
+ testTableStruct("table3", cellsArray, kNoColumnHeader,
+ "Test Table",
+ "this is a test table for nsIAccessibleTable");
+
+ //////////////////////////////////////////////////////////////////////////
+ // row and column spans
+
+ cellsArray = [
+ [kDataCell, kDataCell, kDataCell, kColSpanned, kDataCell, kDataCell, kDataCell, kDataCell],
+ [kDataCell, kDataCell, kDataCell, kDataCell, kDataCell, kDataCell, kDataCell, kRowSpanned],
+ [kDataCell, kDataCell, kColSpanned, kDataCell, kDataCell, kDataCell, kDataCell, kRowSpanned],
+ [kDataCell, kRowSpanned, kSpanned, kDataCell, kDataCell, kRowSpanned, kDataCell, kRowSpanned]
+ ];
+
+ testTableStruct("table4", cellsArray);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Fix our nsHTMLAccessibleTable class so GetIndexAt and GetRowAtIndex and GetColumnAtIndex behave consistently"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=410052">Mozilla Bug 410052</a>
+ <a target="_blank"
+ title="GetCellDataAt callers that expect an error if no cell is found are wrong"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=417912">Mozilla Bug 417912</a>
+ <a target="_blank"
+ title="create accessibles for HTML tr"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=493695">Mozilla Bug 493695</a>
+ <a target="_blank"
+ title="implement IAccessibleTable2"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=512424">Mozilla Bug 512424</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <table id="table1">
+ <thead>
+ <tr>
+ <th rowspan="2">col1</th><th colspan="2">col2</th>
+ </tr>
+ <tr>
+ <th>col2sub1</th><th>col2sub2</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>cell1</td><td>cell2</td><td>cell3</td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr>
+ <th>col1</th><th>col2</th><th>col3</th>
+ </tr>
+ </tfoot>
+ </table>
+
+ <table id="table2">
+ <thead>
+ <tr>
+ <th id="table1_0">col1</th>
+ <th id="table1_1">col2</th>
+ <td id="table1_2" scope="col">col3</td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <th id="table1_3">row1</th>
+ <td id="table1_4">cell1</td>
+ <td id="table1_5">cell2</td>
+ </tr>
+ <tr>
+ <td id="table1_6" scope="row">row2</td>
+ <td id="table1_7">cell3</td>
+ <td id="table1_8">cell4</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table id="table3" border="1"
+ summary="this is a test table for nsIAccessibleTable">
+ <caption>Test Table</caption>
+ <thead>
+ <tr>
+ <th></th>
+ <th>columnHeader_1</th>
+ <th id ="col2a">columnHeader_2</th>
+ <th>columnHeader_3</th>
+ </tr>
+ </thead>
+ <tr>
+ <th id="row2a">rowHeader_1</th>
+ <td id="row2b">row1_column1</td>
+ <td id ="col2b">row1_column2</td>
+ <td id="row2c">row1_column3</td>
+ </tr>
+ <tr>
+ <th>rowHeader_2</th>
+ <td>row2_column1</td>
+ <td id ="col2c">row2_column2</td>
+ <td>row2_column3</td>
+ </tr>
+ </table>
+
+ <table id="table4" cellpadding="2" cellspacing="2" border="1" width="50%">
+ <tbody>
+ <tr>
+ <td><br></td>
+ <td><br></td>
+ <td rowspan="1" colspan="2"><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td rowspan="4" colspan="1"><br></td>
+ </tr>
+ <tr>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ </tr>
+ <tr>
+ <td><br></td>
+ <td rowspan="2" colspan="2">c1</td>
+ <td><br></td>
+ <td><br></td>
+ <td rowspan="2" colspan="1"><br></td>
+ <td><br></td>
+ </tr>
+ <tr>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ <td><br></td>
+ </tr>
+ </tbody>
+ </table>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_struct_tree.xul b/accessible/tests/mochitest/table/test_struct_tree.xul
new file mode 100644
index 000000000..2037bf714
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_struct_tree.xul
@@ -0,0 +1,74 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Table accessible tree and table interface tests for XUL trees">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../table.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "debug";
+
+ function doTest()
+ {
+ var cellsArray = [
+ [kDataCell, kDataCell],
+ [kDataCell, kDataCell],
+ [kDataCell, kDataCell]
+ ];
+
+ testTableStruct("table", cellsArray, kTreeColumnHeader);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yXULTreeLoadEvent(doTest, "table", new nsTableTreeView(3));
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=512424"
+ title="implement IAccessibleTable2">
+ Mozilla Bug 512424
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="debug"/>
+ <tree id="table" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" label="column"/>
+ <treecol id="scol" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren id="treechildren"/>
+ </tree>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/table/test_table_1.html b/accessible/tests/mochitest/table/test_table_1.html
new file mode 100644
index 000000000..ab3937770
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_table_1.html
@@ -0,0 +1,105 @@
+<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en">
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+
+ <script type="application/javascript">
+
+function doTest()
+{
+ var accTable = getAccessible("table", [nsIAccessibleTable]);
+
+ var s = window.getSelection();
+ if (s.rangeCount > 0)
+ s.removeAllRanges();
+
+ var cell = getNode("col2b");
+ var range = document.createRange();
+ range.selectNode(cell);
+ s.addRange(range);
+
+ is(accTable.selectedCellCount, 1, "only one cell selected");
+ cell = getNode("col2a");
+ range = document.createRange();
+ range.selectNode(cell);
+ s.addRange(range);
+ cell = getNode("col2c");
+ range = document.createRange();
+ range.selectNode(cell);
+ s.addRange(range);
+ is(accTable.selectedColumnCount, 1, "only one column selected");
+
+ cell = getNode("row2a");
+ range = document.createRange();
+ range.selectNode(cell);
+ s.addRange(range);
+ cell = getNode("row2b");
+ range = document.createRange();
+ range.selectNode(cell);
+ s.addRange(range);
+ range = document.createRange();
+ cell = getNode("row2c");
+ range.selectNode(cell);
+ s.addRange(range);
+
+ is(accTable.selectedRowCount, 1, "no cells selected");
+
+ var columnDescription = accTable.getColumnDescription(1);
+ var rowDescription = accTable.getRowDescription(1);
+
+ SimpleTest.finish();
+}
+SimpleTest.waitForExplicitFinish();
+addA11yLoadEvent(doTest);
+ </script>
+ </head>
+ <body >
+
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=410052">Mozilla Bug 410052</a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=760878"
+ title="decomtaminate Get Row / Column Description() on accessible tables">
+ Mozilla Bug 760878
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- Test Table -->
+ <br><br><b> Testing Table:</b><br><br>
+ <center>
+ <table id="table" border="1"
+ summary="this is a test table for nsIAccessibleTable" >
+ <caption>Test Table</caption>
+ <thead>
+ <tr>
+ <th></th>
+ <th>columnHeader_1</th>
+ <th id ="col2a">columnHeader_2</th>
+ <th>columnHeader_3</th>
+ </tr>
+ </thead>
+ <tr>
+ <th id="row2a">rowHeader_1</th>
+ <td id="row2b">row1_column1</td>
+ <td id ="col2b">row1_column2</td>
+ <td id="row2c">row1_column3</td>
+ </tr>
+ <tr>
+ <th>rowHeader_2</th>
+ <td>row2_column1</td>
+ <td id ="col2c">row2_column2</td>
+ <td>row2_column3</td>
+ </tr>
+ </table>
+ </center>
+ </body>
+</html>
diff --git a/accessible/tests/mochitest/table/test_table_2.html b/accessible/tests/mochitest/table/test_table_2.html
new file mode 100644
index 000000000..38477c2fa
--- /dev/null
+++ b/accessible/tests/mochitest/table/test_table_2.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en">
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="text/javascript">
+
+function doTest()
+{
+ // Test table with role=alert.
+ var tableInterfaceExposed = true;
+ var accTable3 = getAccessible("table3", [nsIAccessibleTable], null, DONOTFAIL_IF_NO_INTERFACE);
+ if (!accTable3)
+ tableInterfaceExposed = false;
+ ok(tableInterfaceExposed, "table interface is not exposed");
+
+ if (tableInterfaceExposed) {
+ testRole(accTable3, ROLE_ALERT);
+
+ is(accTable3.getCellAt(0,0).firstChild.name, "cell0", "wrong cell");
+ is(accTable3.getCellAt(0,1).firstChild.name, "cell1", "wrong cell");
+ }
+
+ // Test table with role=log and aria property in tr. We create accessible for
+ // tr in this case.
+ tableInterfaceExposed = true;
+ var accTable4 = getAccessible("table4", [nsIAccessibleTable], null, DONOTFAIL_IF_NO_INTERFACE);
+ if (!accTable4)
+ tableInterfaceExposed = false;
+ ok(tableInterfaceExposed, "table interface is not exposed");
+
+ if (tableInterfaceExposed) {
+ accNotCreated = (!isAccessible("tr"));
+ ok(!accNotCreated, "missed tr accessible");
+
+ testRole(accTable4, ROLE_TABLE);
+
+ is(accTable4.getCellAt(0,0).firstChild.name, "cell0", "wrong cell");
+ is(accTable4.getCellAt(0,1).firstChild.name, "cell1", "wrong cell");
+ is(accTable4.getCellAt(1,0).firstChild.name, "cell2", "wrong cell");
+ is(accTable4.getCellAt(1,1).firstChild.name, "cell3", "wrong cell");
+ }
+
+ SimpleTest.finish();
+}
+SimpleTest.waitForExplicitFinish();
+addA11yLoadEvent(doTest);
+ </script>
+ </head>
+
+ <body >
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=419811">Mozilla Bug 419811</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- Test Table -->
+ <br><br><b> Testing Table:</b><br><br>
+ <center>
+ <table id="table3" border="1" role="alert">
+ <tr>
+ <td>cell0</td>
+ <td>cell1</td>
+ </tr>
+ </table>
+
+ <table id="table4" border="1" role="log">
+ <tr aria-live="polite" id="tr">
+ <td>cell0</td>
+ <td>cell1</td>
+ </tr>
+ <tr>
+ <td>cell2</td>
+ <td>cell3</td>
+ </tr>
+ </table>
+
+ </center>
+ </body>
+</html>
diff --git a/accessible/tests/mochitest/test_OuterDocAccessible.html b/accessible/tests/mochitest/test_OuterDocAccessible.html
new file mode 100644
index 000000000..c2efa18fe
--- /dev/null
+++ b/accessible/tests/mochitest/test_OuterDocAccessible.html
@@ -0,0 +1,89 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=441519
+-->
+<head>
+ <title>nsOuterDocAccessible chrome tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="common.js"></script>
+ <script type="application/javascript"
+ src="states.js"></script>
+ <script type="application/javascript"
+ src="role.js"></script>
+
+ <script type="application/javascript">
+ // needed error return value
+ const ns_error_invalid_arg = Components.results.NS_ERROR_INVALID_ARG;
+
+ function doTest()
+ {
+ // Get accessible for body tag.
+ var docAcc = getAccessible(document);
+
+ if (docAcc) {
+ var outerDocAcc = getAccessible(docAcc.parent);
+
+ if (outerDocAcc) {
+ testRole(outerDocAcc, ROLE_INTERNAL_FRAME);
+
+ // check if it is focusable.
+ testStates(outerDocAcc, STATE_FOCUSABLE, 0);
+
+ // see bug 428954: No name wanted for internal frame
+ is(outerDocAcc.name, null, "Wrong name for internal frame!");
+
+ // see bug 440770, no actions wanted on outer doc
+ is(outerDocAcc.actionCount, 0,
+ "Wrong number of actions for internal frame!");
+ var actionTempStr; // not really used, just needs to receive a value
+ try {
+ actionTempStr = outerDocAcc.getActionName(0);
+ do_throw("No exception thrown for actionName!");
+ } catch(e) {
+ ok(e.result, ns_error_invalid_arg,
+ "Wrong return value for actionName call!");
+ }
+
+ try {
+ actionTempStr = outerDocAcc.getActionDescription(0);
+ do_throw("No exception thrown for actionDescription!");
+ } catch(e) {
+ ok(e.result, ns_error_invalid_arg,
+ "Wrong return value for actionDescription call!");
+ }
+
+ try {
+ outerDocAcc.doAction(0);
+ do_throw("No exception thrown for doAction!");
+ } catch(e) {
+ ok(e.result, ns_error_invalid_arg,
+ "Wrong return value for doAction call!");
+ }
+ }
+ }
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=441519"
+ title="nsOuterDocAccessible chrome tests">
+ Mozilla Bug 441519
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/test_aria_token_attrs.html b/accessible/tests/mochitest/test_aria_token_attrs.html
new file mode 100644
index 000000000..882099026
--- /dev/null
+++ b/accessible/tests/mochitest/test_aria_token_attrs.html
@@ -0,0 +1,329 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=452388
+-->
+<head>
+ <title>An NMTOKEN based ARIA property is undefined if the ARIA attribute is not present, or is set to "" or "undefined"</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="common.js"></script>
+ <script type="application/javascript"
+ src="role.js"></script>
+ <script type="application/javascript"
+ src="states.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ // test aria-pressed state mapping to roles PUSHBUTTON vs TOGGLEBUTTON
+ testRole("button_pressed_true", ROLE_TOGGLE_BUTTON);
+ testRole("button_pressed_false", ROLE_TOGGLE_BUTTON);
+ testRole("button_pressed_empty", ROLE_PUSHBUTTON);
+ testRole("button_pressed_undefined", ROLE_PUSHBUTTON);
+ testRole("button_pressed_absent", ROLE_PUSHBUTTON);
+
+ // test button aria-pressed states
+ testStates("button_pressed_true", STATE_PRESSED, 0, STATE_CHECKABLE);
+ testStates("button_pressed_false", 0, 0, STATE_CHECKABLE | STATE_PRESSED);
+ testStates("button_pressed_empty", 0, 0, STATE_PRESSED | STATE_CHECKABLE);
+ testStates("button_pressed_undefined", 0, 0, STATE_PRESSED | STATE_CHECKABLE);
+ testStates("button_pressed_absent", 0, 0, STATE_PRESSED | STATE_CHECKABLE);
+
+ // test (checkbox) checkable and checked states
+ testStates("checkbox_checked_true", (STATE_CHECKABLE | STATE_CHECKED));
+ testStates("checkbox_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("checkbox_checked_empty", STATE_CHECKABLE , 0, STATE_CHECKED);
+ testStates("checkbox_checked_undefined", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("checkbox_checked_absent", STATE_CHECKABLE, 0, STATE_CHECKED);
+
+ // test native checkbox checked state and aria-checked state (if conflict, native wins)
+ testStates("native_checkbox_nativechecked_ariatrue", (STATE_CHECKABLE | STATE_CHECKED));
+ testStates("native_checkbox_nativechecked_ariafalse", (STATE_CHECKABLE | STATE_CHECKED));
+ testStates("native_checkbox_nativechecked_ariaempty", (STATE_CHECKABLE | STATE_CHECKED));
+ testStates("native_checkbox_nativechecked_ariaundefined", (STATE_CHECKABLE | STATE_CHECKED));
+ testStates("native_checkbox_nativechecked_ariaabsent", (STATE_CHECKABLE | STATE_CHECKED));
+
+ testStates("native_checkbox_nativeunchecked_ariatrue", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("native_checkbox_nativeunchecked_ariafalse", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("native_checkbox_nativeunchecked_ariaempty", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("native_checkbox_nativeunchecked_ariaundefined", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("native_checkbox_nativeunchecked_ariaabsent", STATE_CHECKABLE, 0, STATE_CHECKED);
+
+ // test (checkbox) readonly states
+ testStates("checkbox_readonly_true", STATE_READONLY);
+ testStates("checkbox_readonly_false", 0, 0, STATE_READONLY);
+ testStates("checkbox_readonly_empty", 0, 0, STATE_READONLY);
+ testStates("checkbox_readonly_undefined", 0, 0, STATE_READONLY);
+ testStates("checkbox_readonly_absent", 0, 0, STATE_READONLY);
+
+ // test (checkbox) required states
+ testStates("checkbox_required_true", STATE_REQUIRED);
+ testStates("checkbox_required_false", 0, 0, STATE_REQUIRED);
+ testStates("checkbox_required_empty", 0, 0, STATE_REQUIRED);
+ testStates("checkbox_required_undefined", 0, 0, STATE_REQUIRED);
+ testStates("checkbox_required_absent", 0, 0, STATE_REQUIRED);
+
+ // test (checkbox) invalid states
+ testStates("checkbox_invalid_true", STATE_INVALID);
+ testStates("checkbox_invalid_false", 0, 0, STATE_INVALID);
+ testStates("checkbox_invalid_empty", 0, 0, STATE_INVALID);
+ testStates("checkbox_invalid_undefined", 0, 0, STATE_INVALID);
+ testStates("checkbox_invalid_absent", 0, 0, STATE_INVALID);
+
+ // test (checkbox) disabled states
+ testStates("checkbox_disabled_true", STATE_UNAVAILABLE);
+ testStates("checkbox_disabled_false", 0, 0, STATE_UNAVAILABLE);
+ testStates("checkbox_disabled_empty", 0, 0, STATE_UNAVAILABLE);
+ testStates("checkbox_disabled_undefined", 0, 0, STATE_UNAVAILABLE);
+ testStates("checkbox_disabled_absent", 0, 0, STATE_UNAVAILABLE);
+
+ // test (listbox) multiselectable states
+ testStates("listbox_multiselectable_true", STATE_MULTISELECTABLE | STATE_EXTSELECTABLE);
+ testStates("listbox_multiselectable_false", 0, 0, STATE_MULTISELECTABLE | STATE_EXTSELECTABLE);
+ testStates("listbox_multiselectable_empty", 0, 0, STATE_MULTISELECTABLE | STATE_EXTSELECTABLE);
+ testStates("listbox_multiselectable_undefined", 0, 0, STATE_MULTISELECTABLE | STATE_EXTSELECTABLE);
+ testStates("listbox_multiselectable_absent", 0, 0, STATE_MULTISELECTABLE | STATE_EXTSELECTABLE);
+
+ // test (option) checkable and checked states
+ testStates("option_checked_true", (STATE_CHECKABLE | STATE_CHECKED));
+ testStates("option_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("option_checked_empty", 0 , 0, STATE_CHECKABLE | STATE_CHECKED);
+ testStates("option_checked_undefined", 0, 0, STATE_CHECKABLE | STATE_CHECKED);
+ testStates("option_checked_absent", 0, 0, STATE_CHECKABLE | STATE_CHECKED);
+
+ // test (menuitem) checkable and checked states
+ testStates("menuitem_checked_true", (STATE_CHECKABLE | STATE_CHECKED));
+ testStates("menuitem_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("menuitem_checked_empty", 0, 0, (STATE_CHECKABLE | STATE_CHECKED));
+ testStates("menuitem_checked_undefined", 0, 0, (STATE_CHECKABLE | STATE_CHECKED));
+ testStates("menuitem_checked_absent", 0, 0, (STATE_CHECKABLE | STATE_CHECKED));
+
+ // test (menuitemradio) checkable and checked states
+ testStates("menuitemradio_checked_true", (STATE_CHECKABLE | STATE_CHECKED));
+ testStates("menuitemradio_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("menuitemradio_checked_empty", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("menuitemradio_checked_undefined", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("menuitemradio_checked_absent", STATE_CHECKABLE, 0, STATE_CHECKED);
+
+ // test (radio) checkable and checked states
+ testStates("radio_checked_true", (STATE_CHECKABLE | STATE_CHECKED));
+ testStates("radio_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("radio_checked_empty", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("radio_checked_undefined", STATE_CHECKABLE, 0, STATE_CHECKED);
+ testStates("radio_checked_absent", STATE_CHECKABLE, 0, STATE_CHECKED);
+
+ // test (textbox) multiline states
+ testStates("textbox_multiline_true", 0, EXT_STATE_MULTI_LINE);
+ testStates("textbox_multiline_false", 0, EXT_STATE_SINGLE_LINE);
+ testStates("textbox_multiline_empty", 0, EXT_STATE_SINGLE_LINE);
+ testStates("textbox_multiline_undefined", 0, EXT_STATE_SINGLE_LINE);
+ testStates("textbox_multiline_absent", 0, EXT_STATE_SINGLE_LINE);
+
+ // test (textbox) readonly states
+ testStates("textbox_readonly_true", STATE_READONLY);
+ testStates("textbox_readonly_false", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+ testStates("textbox_readonly_empty", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+ testStates("textbox_readonly_undefined", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+ testStates("textbox_readonly_absent", 0, EXT_STATE_EDITABLE, STATE_READONLY);
+
+ // test native textbox readonly state and aria-readonly state (if conflict, native wins)
+ testStates("native_textbox_nativereadonly_ariatrue", STATE_READONLY);
+ testStates("native_textbox_nativereadonly_ariafalse", STATE_READONLY);
+ testStates("native_textbox_nativereadonly_ariaempty", STATE_READONLY);
+ testStates("native_textbox_nativereadonly_ariaundefined", STATE_READONLY);
+ testStates("native_textbox_nativereadonly_ariaabsent", STATE_READONLY);
+
+ testStates("native_textbox_nativeeditable_ariatrue", 0, 0, STATE_READONLY);
+ testStates("native_textbox_nativeeditable_ariafalse", 0, 0, STATE_READONLY);
+ testStates("native_textbox_nativeeditable_ariaempty", 0, 0, STATE_READONLY);
+ testStates("native_textbox_nativeeditable_ariaundefined", 0, 0, STATE_READONLY);
+ testStates("native_textbox_nativeeditable_ariaabsent", 0, 0, STATE_READONLY);
+
+ // test (treeitem) selectable and selected states
+ testStates("treeitem_selected_true", (STATE_SELECTABLE | STATE_SELECTED));
+ testStates("treeitem_selected_false", STATE_SELECTABLE, 0, STATE_SELECTED);
+ testStates("treeitem_selected_empty", STATE_SELECTABLE, 0, STATE_SELECTED);
+ testStates("treeitem_selected_undefined", STATE_SELECTABLE, 0, STATE_SELECTED);
+ testStates("treeitem_selected_absent", STATE_SELECTABLE, 0, STATE_SELECTED);
+
+ // test (treeitem) haspopup states
+ testStates("treeitem_haspopup_true", STATE_HASPOPUP);
+ testStates("treeitem_haspopup_false", 0, 0, STATE_HASPOPUP);
+ testStates("treeitem_haspopup_empty", 0, 0, STATE_HASPOPUP);
+ testStates("treeitem_haspopup_undefined", 0, 0, STATE_HASPOPUP);
+ testStates("treeitem_haspopup_absent", 0, 0, STATE_HASPOPUP);
+
+ // test (treeitem) expandable and expanded/collapsed states
+ testStates("treeitem_expanded_true", STATE_EXPANDED, EXT_STATE_EXPANDABLE);
+ testStates("treeitem_expanded_false", STATE_COLLAPSED, EXT_STATE_EXPANDABLE);
+ testStates("treeitem_expanded_empty", 0, 0, STATE_EXPANDED | STATE_COLLAPSED, EXT_STATE_EXPANDABLE);
+ testStates("treeitem_expanded_undefined", 0, 0, STATE_EXPANDED | STATE_COLLAPSED, EXT_STATE_EXPANDABLE);
+ testStates("treeitem_expanded_absent", 0, 0, STATE_EXPANDED | STATE_COLLAPSED, EXT_STATE_EXPANDABLE);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=452388">
+ Mozilla Bug 452388
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=499653"
+ title="Unify ARIA state attributes mapping rules">
+ Mozilla Bug 499653
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=989958"
+ title="Pressed state is not exposed on a button element with aria-pressed attribute"
+ Mozilla Bug 989958
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="button_pressed_true" role="button" aria-pressed="true">This button has aria-pressed="true" and should get ROLE_TOGGLE_BUTTON. It should also get STATE_PRESSED.</div>
+ <div id="button_pressed_false" role="button" aria-pressed="false">This button has aria-pressed="false" and should get ROLE_TOGGLE_BUTTON.</div>
+ <div id="button_pressed_empty" role="button" aria-pressed="">This button has aria-pressed="" and should <emph>not</emph> get ROLE_BUTTON.</div>
+ <div id="button_pressed_undefined" role="button" aria-pressed="undefined">This button has aria-pressed="undefined" and should <emph>not</emph> get ROLE_TOGGLE_BUTTON.</div>
+ <div id="button_pressed_absent" role="button">This button has <emph>no</emph> aria-pressed attribute and should <emph>not</emph> get ROLE_TOGGLE_BUTTON.</div>
+
+ <div id="checkbox_checked_true" role="checkbox" aria-checked="true">This checkbox has aria-checked="true" and should get STATE_CHECKABLE. It should also get STATE_checked.</div>
+ <div id="checkbox_checked_false" role="checkbox" aria-checked="false">This checkbox has aria-checked="false" and should get STATE_CHECKABLE.</div>
+ <div id="checkbox_checked_empty" role="checkbox" aria-checked="">This checkbox has aria-checked="" and should <emph>not</emph> get STATE_CHECKABLE.</div>
+ <div id="checkbox_checked_undefined" role="checkbox" aria-checked="undefined">This checkbox has aria-checked="undefined" and should <emph>not</emph> get STATE_CHECKABLE.</div>
+ <div id="checkbox_checked_absent" role="checkbox">This checkbox has <emph>no</emph> aria-checked attribute and should get STATE_CHECKABLE.</div>
+
+ <form action="">
+ <input id="native_checkbox_nativechecked_ariatrue" type="checkbox" checked="checked" aria-checked="true"/>
+ <input id="native_checkbox_nativechecked_ariafalse" type="checkbox" checked="checked" aria-checked="false"/>
+ <input id="native_checkbox_nativechecked_ariaempty" type="checkbox" checked="checked" aria-checked=""/>
+ <input id="native_checkbox_nativechecked_ariaundefined" type="checkbox" checked="checked" aria-checked="undefined"/>
+ <input id="native_checkbox_nativechecked_ariaabsent" type="checkbox" checked="checked"/>
+
+ <input id="native_checkbox_nativeunchecked_ariatrue" type="checkbox" aria-checked="true"/>
+ <input id="native_checkbox_nativeunchecked_ariafalse" type="checkbox" aria-checked="false"/>
+ <input id="native_checkbox_nativeunchecked_ariaempty" type="checkbox" aria-checked=""/>
+ <input id="native_checkbox_nativeunchecked_ariaundefined" type="checkbox" aria-checked="undefined"/>
+ <input id="native_checkbox_nativeunchecked_ariaabsent" type="checkbox"/>
+ </form>
+
+ <div id="checkbox_readonly_true" role="checkbox" aria-readonly="true">This checkbox has aria-readonly="true" and should get STATE_READONLY.</div>
+ <div id="checkbox_readonly_false" role="checkbox" aria-readonly="false">This checkbox has aria-readonly="false" and should <emph>not</emph> get STATE_READONLY.</div>
+ <div id="checkbox_readonly_empty" role="checkbox" aria-readonly="">This checkbox has aria-readonly="" and should <emph>not</emph> get STATE_READONLY.</div>
+ <div id="checkbox_readonly_undefined" role="checkbox" aria-readonly="undefined">This checkbox has aria-readonly="undefined" and should <emph>not</emph> get STATE_READONLY.</div>
+ <div id="checkbox_readonly_absent" role="checkbox">This checkbox has <emph>no</emph> aria-readonly attribute and should <emph>not</emph> get STATE_READONLY.</div>
+
+ <div id="checkbox_required_true" role="checkbox" aria-required="true">This checkbox has aria-required="true" and should get STATE_REQUIRED.</div>
+ <div id="checkbox_required_false" role="checkbox" aria-required="false">This checkbox has aria-required="false" and should <emph>not</emph> get STATE_REQUIRED.</div>
+ <div id="checkbox_required_empty" role="checkbox" aria-required="">This checkbox has aria-required="" and should <emph>not</emph> get STATE_REQUIRED.</div>
+ <div id="checkbox_required_undefined" role="checkbox" aria-required="undefined">This checkbox has aria-required="undefined" and should <emph>not</emph> get STATE_REQUIRED.</div>
+ <div id="checkbox_required_absent" role="checkbox">This checkbox has <emph>no</emph> aria-required attribute and should <emph>not</emph> get STATE_REQUIRED.</div>
+
+ <div id="checkbox_invalid_true" role="checkbox" aria-invalid="true">This checkbox has aria-invalid="true" and should get STATE_INVALID.</div>
+ <div id="checkbox_invalid_false" role="checkbox" aria-invalid="false">This checkbox has aria-invalid="false" and should <emph>not</emph> get STATE_INVALID.</div>
+ <div id="checkbox_invalid_empty" role="checkbox" aria-invalid="">This checkbox has aria-invalid="" and should <emph>not</emph> get STATE_INVALID.</div>
+ <div id="checkbox_invalid_undefined" role="checkbox" aria-invalid="undefined">This checkbox has aria-invalid="undefined" and should <emph>not</emph> get STATE_INVALID.</div>
+ <div id="checkbox_invalid_absent" role="checkbox">This checkbox has <emph>no</emph> aria-invalid attribute and should <emph>not</emph> get STATE_INVALID.</div>
+
+ <div id="checkbox_disabled_true" role="checkbox" aria-disabled="true" tabindex="0">This checkbox has aria-disabled="true" and should get STATE_DISABLED.</div>
+ <div id="checkbox_disabled_false" role="checkbox" aria-disabled="false">This checkbox has aria-disabled="false" and should <emph>not</emph> get STATE_DISABLED.</div>
+ <div id="checkbox_disabled_empty" role="checkbox" aria-disabled="">This checkbox has aria-disabled="" and should <emph>not</emph> get STATE_DISABLED.</div>
+ <div id="checkbox_disabled_undefined" role="checkbox" aria-disabled="undefined">This checkbox has aria-disabled="undefined" and should <emph>not</emph> get STATE_DISABLED.</div>
+ <div id="checkbox_disabled_absent" role="checkbox">This checkbox has <emph>no</emph> aria-disabled attribute and should <emph>not</emph> get STATE_DISABLED.</div>
+
+ <div id="listbox_multiselectable_true" role="listbox" aria-multiselectable="true">
+ <div id="option_checked_true" role="option" aria-checked="true">item</div>
+ </div>
+ <div id="listbox_multiselectable_false" role="listbox" aria-multiselectable="false">
+ <div id="option_checked_false" role="option" aria-checked="false">item</div>
+ </div>
+ <div id="listbox_multiselectable_empty" role="listbox" aria-multiselectable="">
+ <div id="option_checked_empty" role="option" aria-checked="">item</div>
+ </div>
+ <div id="listbox_multiselectable_undefined" role="listbox" aria-multiselectable="undefined">
+ <div id="option_checked_undefined" role="option" aria-checked="undefined">item</div>
+ </div>
+ <div id="listbox_multiselectable_absent" role="listbox">
+ <div id="option_checked_absent" role="option">item</div>
+ </div>
+
+ <div role="menu">
+ <div id="menuitem_checked_true" role="menuitem" aria-checked="true">This menuitem has aria-checked="true" and should get STATE_CHECKABLE. It should also get STATE_checked.</div>
+ <div id="menuitem_checked_false" role="menuitem" aria-checked="false">This menuitem has aria-checked="false" and should get STATE_CHECKABLE.</div>
+ <div id="menuitem_checked_empty" role="menuitem" aria-checked="">This menuitem has aria-checked="" and should <emph>not</emph> get STATE_CHECKABLE.</div>
+ <div id="menuitem_checked_undefined" role="menuitem" aria-checked="undefined">This menuitem has aria-checked="undefined" and should <emph>not</emph> get STATE_CHECKABLE.</div>
+ <div id="menuitem_checked_absent" role="menuitem">This menuitem has <emph>no</emph> aria-checked attribute and should <emph>not</emph> get STATE_CHECKABLE.</div>
+
+ <div id="menuitemradio_checked_true" role="menuitemradio" aria-checked="true">This menuitem has aria-checked="true" and should get STATE_CHECKABLE. It should also get STATE_checked.</div>
+ <div id="menuitemradio_checked_false" role="menuitemradio" aria-checked="false">This menuitem has aria-checked="false" and should get STATE_CHECKABLE.</div>
+ <div id="menuitemradio_checked_empty" role="menuitemradio" aria-checked="">This menuitem has aria-checked="" and should <emph>not</emph> get STATE_CHECKABLE.</div>
+ <div id="menuitemradio_checked_undefined" role="menuitemradio" aria-checked="undefined">This menuitem has aria-checked="undefined" and should <emph>not</emph> get STATE_CHECKABLE.</div>
+ <div id="menuitemradio_checked_absent" role="menuitemradio">This menuitem has <emph>no</emph> aria-checked attribute but should get STATE_CHECKABLE.</div>
+ </div>
+
+ <div id="radio_checked_true" role="radio" aria-checked="true">This menuitem has aria-checked="true" and should get STATE_CHECKABLE. It should also get STATE_CHECKED.</div>
+ <div id="radio_checked_false" role="radio" aria-checked="false">This menuitem has aria-checked="false" and should get STATE_CHECKABLE.</div>
+ <div id="radio_checked_empty" role="radio" aria-checked="">This menuitem has aria-checked="" and should <emph>not</emph> get STATE_CHECKABLE.</div>
+ <div id="radio_checked_undefined" role="radio" aria-checked="undefined">This menuitem has aria-checked="undefined" and should <emph>not</emph> get STATE_CHECKABLE.</div>
+ <div id="radio_checked_absent" role="radio">This menuitem has <emph>no</emph> aria-checked attribute but should get STATE_CHECKABLE.</div>
+
+ <div id="textbox_readonly_true" role="textbox" aria-readonly="true"></div>
+ <div id="textbox_readonly_false" role="textbox" aria-readonly="false"></div>
+ <div id="textbox_readonly_empty" role="textbox" aria-readonly=""></div>
+ <div id="textbox_readonly_undefined" role="textbox" aria-readonly="undefined"></div>
+ <div id="textbox_readonly_absent" role="textbox"></div>
+
+ <div id="textbox_multiline_true" role="textbox" aria-multiline="true"></div>
+ <div id="textbox_multiline_false" role="textbox" aria-multiline="false"></div>
+ <div id="textbox_multiline_empty" role="textbox" aria-multiline=""></div>
+ <div id="textbox_multiline_undefined" role="textbox" aria-multiline="undefined"></div>
+ <div id="textbox_multiline_absent" role="textbox"></div>
+
+ <form action="">
+ <input id="native_textbox_nativereadonly_ariatrue" readonly="readonly" aria-readonly="true"/>
+ <input id="native_textbox_nativereadonly_ariafalse" readonly="readonly" aria-readonly="false"/>
+ <input id="native_textbox_nativereadonly_ariaempty" readonly="readonly" aria-readonly=""/>
+ <input id="native_textbox_nativereadonly_ariaundefined" readonly="readonly" aria-readonly="undefined"/>
+ <input id="native_textbox_nativereadonly_ariaabsent" readonly="readonly"/>
+
+ <input id="native_textbox_nativeeditable_ariatrue" aria-readonly="true"/>
+ <input id="native_textbox_nativeeditable_ariafalse" aria-readonly="false"/>
+ <input id="native_textbox_nativeeditable_ariaempty" aria-readonly=""/>
+ <input id="native_textbox_nativeeditable_ariaundefined" aria-readonly="undefined"/>
+ <input id="native_textbox_nativeeditable_ariaabsent"/>
+ </form>
+
+ <div role="tree">
+ <div id="treeitem_selected_true" role="treeitem" aria-selected="true">This treeitem has aria-selected="true" and should get STATE_SELECTABLE. It should also get STATE_SELECTED.</div>
+ <div id="treeitem_selected_false" role="treeitem" aria-selected="false">This treeitem has aria-selected="false" and should get STATE_SELECTABLE.</div>
+ <div id="treeitem_selected_empty" role="treeitem" aria-selected="">This treeitem has aria-selected="" and should <emph>not</emph> get STATE_SELECTABLE.</div>
+ <div id="treeitem_selected_undefined" role="treeitem" aria-selected="undefined">This treeitem has aria-selected="undefined" and should <emph>not</emph> get STATE_SELECTABLE.</div>
+ <div id="treeitem_selected_absent" role="treeitem">This treeitem has <emph>no</emph> aria-selected attribute and should <emph>not</emph> get STATE_SELECTABLE.</div>
+
+ <div id="treeitem_haspopup_true" role="treeitem" aria-haspopup="true">This treeitem has aria-haspopup="true" and should get STATE_HASPOPUP.</div>
+ <div id="treeitem_haspopup_false" role="treeitem" aria-haspopup="false">This treeitem has aria-haspopup="false" and should get STATE_HASPOPUP.</div>
+ <div id="treeitem_haspopup_empty" role="treeitem" aria-haspopup="">This treeitem has aria-haspopup="" and should <emph>not</emph> get STATE_HASPOPUP.</div>
+ <div id="treeitem_haspopup_undefined" role="treeitem" aria-haspopup="undefined">This treeitem has aria-haspopup="undefined" and should <emph>not</emph> get STATE_HASPOPUP.</div>
+ <div id="treeitem_haspopup_absent" role="treeitem">This treeitem has <emph>no</emph> aria-haspopup attribute and should <emph>not</emph> get STATE_HASPOPUP.</div>
+
+ <div id="treeitem_expanded_true" role="treeitem" aria-expanded="true">This treeitem has aria-expanded="true" and should get STATE_EXPANDABLE. It should also get STATE_EXPANDED.</div>
+ <div id="treeitem_expanded_false" role="treeitem" aria-expanded="false">This treeitem has aria-expanded="false" and should get STATE_EXPANDABLE. It should also get STATE_COLLAPSED.</div>
+ <div id="treeitem_expanded_empty" role="treeitem" aria-expanded="">This treeitem has aria-expanded="" and should <emph>not</emph> get STATE_EXPANDABLE.</div>
+ <div id="treeitem_expanded_undefined" role="treeitem" aria-expanded="undefined">This treeitem has aria-expanded="undefined" and should <emph>not</emph> get STATE_EXPANDABLE.</div>
+ <div id="treeitem_expanded_absent" role="treeitem">This treeitem has <emph>no</emph> aria-expanded attribute and should <emph>not</emph> get STATE_EXPANDABLE.</div>
+ </div>
+
+ </body>
+</html>
diff --git a/accessible/tests/mochitest/test_bug420863.html b/accessible/tests/mochitest/test_bug420863.html
new file mode 100644
index 000000000..63ab4d37b
--- /dev/null
+++ b/accessible/tests/mochitest/test_bug420863.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=420863
+-->
+<head>
+ <title>Table indexes chrome tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="common.js"></script>
+ <script type="application/javascript"
+ src="events.js"></script>
+ <script type="application/javascript"
+ src="actions.js"></script>
+
+ <script type="application/javascript">
+ var gClickHandler = null;
+
+ function doTest()
+ {
+ // Actions should be exposed on any accessible having related DOM node
+ // with registered 'click' event handler.
+
+ //////////////////////////////////////////////////////////////////////////
+ // generic td
+ var td1Acc = getAccessible("td1");
+ if (!td1Acc) {
+ SimpleTest.finish();
+ return;
+ }
+
+ is(td1Acc.actionCount, 0,
+ "Simple table cell shouldn't have any actions");
+
+ //////////////////////////////////////////////////////////////////////////
+ // one td with 'onclick' attribute and one with registered click handler
+ var td3Node = getNode("td3");
+
+ // register 'click' event handler
+ gClickHandler = {
+ handleEvent: function handleEvent(aEvent)
+ {
+ }
+ };
+ td3Node.addEventListener("click", gClickHandler, false);
+
+ // check actions
+ var actionsArray = [
+ {
+ ID: "td2", // "onclick" attribute
+ actionName: "click",
+ actionIndex: 0,
+ events: CLICK_EVENTS
+ },
+ {
+ ID: td3Node,
+ actionName: "click",
+ actionIndex: 0,
+ events: CLICK_EVENTS,
+ checkOnClickEvent: function check(aEvent)
+ {
+ // unregister click event handler
+ this.ID.removeEventListener("click", gClickHandler, false);
+
+ // check actions
+ is(getAccessible(this.ID).actionCount, 0,
+ "td3 shouldn't have actions");
+ }
+ }
+ ];
+
+ testActions(actionsArray); // will call SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=420863"
+ title="If an HTML element has an onClick attribute, expose its click action on the element rather than its child text leaf node."
+ target="_blank">Mozilla Bug 420863</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <table>
+ <tr>
+ <td id="td1">Can't click this cell</td>
+ <td onclick="gTdClickAttr = true;"
+ id="td2">Cell with 'onclick' attribute</td>
+ <td id="td3">Cell with registered 'click' event handler</td>
+ </tr>
+ </table>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/test_descr.html b/accessible/tests/mochitest/test_descr.html
new file mode 100644
index 000000000..b9dc031d5
--- /dev/null
+++ b/accessible/tests/mochitest/test_descr.html
@@ -0,0 +1,121 @@
+<html>
+
+<head>
+ <title>nsIAccessible::description tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="common.js"></script>
+ <script type="application/javascript"
+ src="name.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ // Description from aria-describedby attribute
+ testDescr("img1", "aria description");
+
+ // No description from @title attribute because it is used to generate
+ // name.
+ testDescr("img2", "");
+
+ // Description from @title attribute, name is generated from @alt
+ // attribute.
+ testDescr("img3", "description");
+
+ // No description from aria-describedby since it is the same as the
+ // @alt attribute which is used as the name
+ testDescr("img4", "");
+
+ // No description from @title attribute since it is the same as the
+ // @alt attribute which is used as the name
+ testDescr("img5", "");
+
+ // Description from content of h2.
+ testDescr("p", "heading");
+
+ // From table summary (caption is used as a name)
+ testDescr("table1", "summary");
+
+ // Empty (summary is used as a name)
+ testDescr("table2", "");
+
+ // From title (summary is used as a name)
+ testDescr("table3", "title");
+
+ // No description from <desc> element since it is the same as the
+ // <title> element.
+ testDescr("svg", "");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=489944"
+ title="@title attribute no longer exposed on accDescription">
+ Mozilla Bug 489944
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=666212"
+ title="summary attribute content mapped to accessible name in MSAA">
+ Mozilla Bug 666212
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi/id=1031188"
+ title="Ensure that accDescription never duplicates AccessibleName">
+ Mozilla Bug 1031188
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <p id="description">aria description</p>
+ <img id="img1" aria-describedby="description" />
+ <img id="img2" title="title" />
+ <img id="img3" alt="name" title="description" />
+ <img id="img4" alt="aria description" aria-describedby="description">
+ <img id="img5" alt="image" title="image">
+
+ <h2 id="heading">heading</h2>
+ <p id="p" aria-describedby="heading" role="button">click me</p>
+
+ <table id="table1" summary="summary">
+ <caption>caption</caption>
+ <tr><td>cell</td></tr>
+ </table>
+
+ <table id="table2" summary="summary">
+ <tr><td>cell</td></tr>
+ </table>
+
+ <table id="table3" summary="summary" title="title">
+ <tr><td>cell</td></tr>
+ </table>
+
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1"
+ viewBox="0 0 100 100" preserveAspectRatio="xMidYMid slice"
+ id="svg"
+ style="width:100px; height:100px;">
+ <title>SVG Image</title>
+ <desc>SVG Image</desc>
+ <linearGradient id="gradient">
+ <stop class="begin" offset="0%"/>
+ <stop class="end" offset="100%"/>
+ </linearGradient>
+ <rect x="0" y="0" width="100" height="100" style="fill:url(#gradient)" />
+ <circle cx="50" cy="50" r="30" style="fill:url(#gradient)" />
+ </svg>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/test_nsIAccessibleDocument.html b/accessible/tests/mochitest/test_nsIAccessibleDocument.html
new file mode 100644
index 000000000..9e0dc7489
--- /dev/null
+++ b/accessible/tests/mochitest/test_nsIAccessibleDocument.html
@@ -0,0 +1,96 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=441737
+-->
+<head>
+ <title>nsIAccessibleDocument chrome tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="common.js"></script>
+ <script type="application/javascript"
+ src="role.js"></script>
+ <script type="application/javascript"
+ src="states.js"></script>
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/chrome-harness.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ var docAcc = getAccessible(document, [nsIAccessibleDocument]);
+ if (docAcc) {
+ // nsIAccessible
+ is(docAcc.name, "nsIAccessibleDocument chrome tests",
+ "Name for document accessible not correct!");
+
+ testRole(docAcc, ROLE_DOCUMENT);
+
+ // check if it is focusable, read-only.
+ testStates(docAcc, (STATE_READONLY | STATE_FOCUSABLE));
+
+ // No actions wanted on doc
+ is(docAcc.actionCount, 0, "Wrong number of actions for document!");
+
+ // attributes should contain tag:body
+ attributes = docAcc.attributes;
+ is(attributes.getStringProperty("tag"), "body",
+ "Wrong attribute on document!");
+
+ // Document URL.
+ var rootDir = getRootDirectory(window.location.href);
+ is(docAcc.URL, rootDir + "test_nsIAccessibleDocument.html",
+ "Wrong URL for document!");
+
+ // Document title and mime type.
+ is(docAcc.title, "nsIAccessibleDocument chrome tests",
+ "Wrong title for document!");
+ is(docAcc.mimeType, "text/html",
+ "Wrong mime type for document!");
+
+ // DocAccessible::getDocType currently returns NS_ERROR_FAILURE.
+ // See bug 442005. After fixing, please remove this comment and
+ // uncomment the below two lines to enable the test.
+// is(docAcc.docType, "HTML",
+// "Wrong type of document!");
+
+ // Test for correct nsIDOMDocument retrieval.
+ var domDoc = null;
+ try {
+ domDoc = docAcc.DOMDocument.QueryInterface(nsIDOMDocument);
+ } catch(e) {}
+ ok(domDoc, "no nsIDOMDocument for this doc accessible!");
+ is(domDoc, document, "Document nodes do not match!");
+
+ // Test for correct nsIDOMWindow retrieval.
+ var domWindow = null;
+ try {
+ domWindow = docAcc.window.QueryInterface(nsIDOMWindow);
+ } catch(e) {}
+ ok(domWindow, "no nsIDOMWindow for this doc accessible!");
+ is(domWindow, window, "Window nodes do not match!");
+ }
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=441737"
+ title="nsAccessibleDocument chrome tests">
+ Mozilla Bug 441737
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/test_nsIAccessibleImage.html b/accessible/tests/mochitest/test_nsIAccessibleImage.html
new file mode 100644
index 000000000..39e7e41e1
--- /dev/null
+++ b/accessible/tests/mochitest/test_nsIAccessibleImage.html
@@ -0,0 +1,202 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=429659
+-->
+<head>
+ <title>nsIAccessibleImage chrome tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="common.js"></script>
+ <script type="application/javascript"
+ src="role.js"></script>
+ <script type="application/javascript"
+ src="attributes.js"></script>
+ <script type="application/javascript"
+ src="layout.js"></script>
+
+ <script type="application/javascript">
+ function testCoordinates(aID, aAcc, aWidth, aHeight)
+ {
+ var screenX = {}, screenY = {}, windowX = {}, windowY = {}, parentX = {},
+ parentY = {};
+
+ // get screen coordinates.
+ aAcc.getImagePosition(
+ nsIAccessibleCoordinateType.COORDTYPE_SCREEN_RELATIVE,
+ screenX, screenY);
+ // get window coordinates.
+ aAcc.getImagePosition(
+ nsIAccessibleCoordinateType.COORDTYPE_WINDOW_RELATIVE,
+ windowX, windowY);
+ // get parent related coordinates.
+ aAcc.getImagePosition(
+ nsIAccessibleCoordinateType.COORDTYPE_PARENT_RELATIVE,
+ parentX, parentY);
+ // XXX For linked images, a negative parentY value is returned, and the
+ // screenY coordinate is the link's screenY coordinate minus 1.
+ // Until this is fixed, set parentY to -1 if it's negative.
+ if (parentY.value < 0)
+ parentY.value = -1;
+
+ // See if asking image for child at image's screen coordinates gives
+ // correct accessible. getChildAtPoint operates on screen coordinates.
+ var tempAcc = null;
+ try {
+ tempAcc = aAcc.getChildAtPoint(screenX.value, screenY.value);
+ } catch(e) {}
+ is(tempAcc, aAcc,
+ "Wrong accessible returned for position of " + aID + "!");
+
+ // get image's parent.
+ var imageParentAcc = null;
+ try {
+ imageParentAcc = aAcc.parent;
+ } catch(e) {}
+ ok(imageParentAcc, "no parent accessible for " + aID + "!");
+
+ if (imageParentAcc) {
+ // See if parent's screen coordinates plus image's parent relative
+ // coordinates equal to image's screen coordinates.
+ var parentAccX = {}, parentAccY = {}, parentAccWidth = {},
+ parentAccHeight = {};
+ imageParentAcc.getBounds(parentAccX, parentAccY, parentAccWidth,
+ parentAccHeight);
+ is(parentAccX.value + parentX.value, screenX.value,
+ "Wrong screen x coordinate for " + aID + "!");
+// XXX see bug 456344 is(parentAccY.value + parentY.value, screenY.value,
+// "Wrong screen y coordinate for " + aID + "!");
+ }
+
+ var [expected_w, expected_h] = CSSToDevicePixels(window, aWidth, aHeight);
+ var width = {}, height = {};
+ aAcc.getImageSize(width, height);
+ is(width.value, expected_w, "Wrong width for " + aID + "!");
+ is(height.value, expected_h, "wrong height for " + aID + "!");
+ }
+
+ function testThis(aID, aSRC, aWidth, aHeight,
+ aActionCount, aActionNames)
+ {
+ var acc = getAccessible(aID, [nsIAccessibleImage]);
+ if (!acc)
+ return;
+
+ // Test role
+ testRole(aID, ROLE_GRAPHIC);
+
+ // test coordinates and size
+ testCoordinates(aID, acc, aWidth, aHeight);
+
+ // bug 429659: Make sure the SRC attribute is set for any image
+ var attributes = {"src": aSRC};
+ testAttrs(acc, attributes, true);
+
+ var actionCount = aActionCount || 0;
+ is(acc.actionCount, actionCount,
+ "Wrong number of actions for " + aID + "!");
+ if (actionCount) {
+ for (index = 0; index < aActionNames.length; index++) {
+ is(acc.getActionName(index), aActionNames[index],
+ "Wrong action name for " + aID + ", index " + index +"!");
+ }
+ }
+ }
+
+ function doTest()
+ {
+ // Test non-linked image
+ testThis("nonLinkedImage", "moz.png", 89, 38);
+
+ // Test linked image
+ var actionNamesArray = new Array("jump");
+ testThis("linkedImage", "moz.png", 89, 38, 1,
+ actionNamesArray);
+
+ // Image with long desc
+ var actionNamesArray = new Array("showlongdesc");
+ testThis("longdesc", "moz.png", 89, 38, 1,
+ actionNamesArray);
+
+ // Image with invalid url in long desc
+ testThis("invalidLongdesc", "moz.png", 89, 38, 0);
+
+ // Image with click and long desc
+ actionNamesArray = null;
+ actionNamesArray = new Array("click", "showlongdesc");
+ testThis("clickAndLongdesc", "moz.png",
+ 89, 38, 2, actionNamesArray);
+
+ // Image with click
+ actionNamesArray = null;
+ actionNamesArray = new Array("click");
+ testThis("click", "moz.png",
+ 89, 38, 1, actionNamesArray);
+
+ // Image with long desc
+ actionNamesArray = null;
+ actionNamesArray = new Array("showlongdesc");
+ testThis("longdesc2", "moz.png",
+ 89, 38, 1, actionNamesArray);
+
+ // Image described by HTML:a@href with whitespaces
+ actionNamesArray = null;
+ actionNamesArray = new Array("showlongdesc");
+ testThis("longdesc3", "moz.png",
+ 89, 38, 1, actionNamesArray);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=429659">Mozilla Bug 429659</a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=652635"
+ title="fall back missing @longdesc to aria-describedby pointing to a href">
+ Mozilla Bug 652635
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <br>Simple image:<br>
+ <img id="nonLinkedImage" src="moz.png"/>
+ <br>Linked image:<br>
+ <a href="http://www.mozilla.org"><img id="linkedImage" src="moz.png"></a>
+ <br>Image with longdesc:<br>
+ <img id="longdesc" src="moz.png" longdesc="longdesc_src.html"
+ alt="Image of Mozilla logo"/>
+ <br>Image with invalid url in longdesc:<br>
+ <img id="invalidLongdesc" src="moz.png" longdesc="longdesc src.html"
+ alt="Image of Mozilla logo"/>
+ <br>Image with click and longdesc:<br>
+ <img id="clickAndLongdesc" src="moz.png" longdesc="longdesc_src.html"
+ alt="Another image of Mozilla logo" onclick="alert('Clicked!');"/>
+
+ <br>image described by a link to be treated as longdesc<br>
+ <img id="longdesc2" src="moz.png" aria-describedby="describing_link"
+ alt="Second Image of Mozilla logo"/>
+ <a id="describing_link" href="longdesc_src.html">link to description of image</a>
+
+ <br>Image described by a link to be treated as longdesc with whitespaces<br>
+ <img id="longdesc3" src="moz.png" aria-describedby="describing_link2"
+ alt="Second Image of Mozilla logo"/>
+ <a id="describing_link2" href="longdesc src.html">link to description of image</a>
+
+ <br>Image with click:<br>
+ <img id="click" src="moz.png"
+ alt="A third image of Mozilla logo" onclick="alert('Clicked, too!');"/>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/text.js b/accessible/tests/mochitest/text.js
new file mode 100644
index 000000000..a91ea9acc
--- /dev/null
+++ b/accessible/tests/mochitest/text.js
@@ -0,0 +1,634 @@
+////////////////////////////////////////////////////////////////////////////////
+// Public
+
+const BOUNDARY_CHAR = nsIAccessibleText.BOUNDARY_CHAR;
+const BOUNDARY_WORD_START = nsIAccessibleText.BOUNDARY_WORD_START;
+const BOUNDARY_WORD_END = nsIAccessibleText.BOUNDARY_WORD_END;
+const BOUNDARY_LINE_START = nsIAccessibleText.BOUNDARY_LINE_START;
+const BOUNDARY_LINE_END = nsIAccessibleText.BOUNDARY_LINE_END;
+
+const kTextEndOffset = nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT;
+const kCaretOffset = nsIAccessibleText.TEXT_OFFSET_CARET;
+
+const EndPoint_Start = nsIAccessibleTextRange.EndPoint_Start;
+const EndPoint_End = nsIAccessibleTextRange.EndPoint_End;
+
+const kTodo = 1; // a test is expected to fail
+const kOk = 2; // a test doesn't fail
+
+/**
+ * Test characterCount for the given array of accessibles.
+ *
+ * @param aCount [in] the expected character count
+ * @param aIDs [in] array of accessible identifiers to test
+ * @param aTodoFlag [in, optional] either kOk or kTodo
+ */
+function testCharacterCount(aIDs, aCount, aTodoFlag)
+{
+ var ids = (aIDs instanceof Array) ? aIDs : [ aIDs ];
+ var isFunc = (aTodoFlag == kTodo) ? todo_is : is;
+ for (var i = 0; i < ids.length; i++) {
+ var textacc = getAccessible(ids[i], [nsIAccessibleText]);
+ isFunc(textacc.characterCount, aCount,
+ "Wrong character count for " + prettyName(ids[i]));
+ }
+}
+
+/**
+ * Test text between two given offsets.
+ *
+ * @param aIDs [in] an array of accessible IDs to test
+ * @param aStartOffset [in] the start offset within the text to test
+ * @param aEndOffset [in] the end offset up to which the text is tested
+ * @param aText [in] the expected result from the test
+ * @param aTodoFlag [in, optional] either kOk or kTodo
+ */
+function testText(aIDs, aStartOffset, aEndOffset, aText, aTodoFlag)
+{
+ var ids = (aIDs instanceof Array) ? aIDs : [ aIDs ];
+ var isFunc = (aTodoFlag == kTodo) ? todo_is : is;
+ for (var i = 0; i < ids.length; i++) {
+ var acc = getAccessible(ids[i], nsIAccessibleText);
+ try {
+ isFunc(acc.getText(aStartOffset, aEndOffset), aText,
+ "getText: wrong text between start and end offsets '" +
+ aStartOffset + "', '" + aEndOffset + " for '" +
+ prettyName(ids[i]) + "'");
+ } catch (e) {
+ ok(false,
+ "getText fails between start and end offsets '" + aStartOffset +
+ "', '" + aEndOffset + " for '" + prettyName(ids[i]) + "'");
+ }
+ }
+}
+
+/**
+ * Test password text between two given offsets
+ *
+ * @param aIDs [in] an array of accessible IDs to test
+ * @param aStartOffset [in] the start offset within the text to test
+ * @param aEndOffset [in] the end offset up to which the text is tested
+ * @param aText [in] the expected result from the test
+ *
+ * @note All this function does is test that getText doe snot expose the
+ * password text itself, but something else.
+ */
+function testPasswordText(aIDs, aStartOffset, aEndOffset, aText)
+{
+ for (var i = 0; i < aIDs.length; i++)
+ {
+ var acc = getAccessible(aIDs[i], nsIAccessibleText);
+ try {
+ isnot(acc.getText(aStartOffset, aEndOffset), aText,
+ "getText: plain text between start and end offsets '" + aStartOffset +
+ "', '" + aEndOffset + " for '" + prettyName(aIDs[i]) + "'");
+ } catch (e) {
+ ok(false,
+ "getText fails between start and end offsets '" + aStartOffset +
+ "', '" + aEndOffset + " for '" + prettyName(aIDs[i]) + "'");
+ }
+ }
+}
+
+/**
+ * Test getTextAtOffset for BOUNDARY_CHAR over different elements.
+ *
+ * @param aIDs [in] the accessible identifier or array of accessible
+ * identifiers
+ * @param aOffset [in] the offset to get a character at it
+ * @param aChar [in] the expected character
+ * @param aStartOffset [in] expected start offset of the character
+ * @param aEndOffset [in] expected end offset of the character
+ */
+function testCharAtOffset(aIDs, aOffset, aChar, aStartOffset, aEndOffset)
+{
+ var IDs = (aIDs instanceof Array) ? aIDs : [ aIDs ];
+ for (var i = 0; i < IDs.length; i++) {
+ var acc = getAccessible(IDs[i], nsIAccessibleText);
+ testTextHelper(IDs[i], aOffset, BOUNDARY_CHAR,
+ aChar, aStartOffset, aEndOffset,
+ kOk, kOk, kOk,
+ acc.getTextAtOffset, "getTextAtOffset ");
+ }
+}
+
+/**
+ * Test getTextAtOffset function over different elements.
+ *
+ * @param aIDs [in] ID or array of IDs
+ * @param aBoundaryType [in] boundary type for text to be retrieved
+ * @param aTestList [in] array of sets:
+ * offset1 and offset2 defining the offset range
+ * the text in the range
+ * start offset of the text in the range
+ * end offset of the text in the range
+ *
+ * or
+ *
+ * @param aOffset [in] the offset to get the text at
+ * @param aBoundaryType [in] Boundary type for text to be retrieved
+ * @param aText [in] expected return text for getTextAtOffset
+ * @param aStartOffset [in] expected return start offset for getTextAtOffset
+ * @param aEndOffset [in] expected return end offset for getTextAtOffset
+ * @param ... [in] list of ids or list of tuples made of:
+ * element identifier
+ * kTodo or kOk for returned text
+ * kTodo or kOk for returned start offset
+ * kTodo or kOk for returned offset result
+ */
+function testTextAtOffset()
+{
+ testTextSuperHelper("getTextAtOffset", arguments);
+}
+
+/**
+ * Test getTextAfterOffset for BOUNDARY_CHAR over different elements.
+ *
+ * @param aIDs [in] the accessible identifier or array of accessible
+ * identifiers
+ * @param aOffset [in] the offset to get a character after it
+ * @param aChar [in] the expected character
+ * @param aStartOffset [in] expected start offset of the character
+ * @param aEndOffset [in] expected end offset of the character
+ */
+function testCharAfterOffset(aIDs, aOffset, aChar, aStartOffset, aEndOffset)
+{
+ var IDs = (aIDs instanceof Array) ? aIDs : [ aIDs ];
+ for (var i = 0; i < IDs.length; i++) {
+ var acc = getAccessible(IDs[i], nsIAccessibleText);
+ testTextHelper(IDs[i], aOffset, BOUNDARY_CHAR,
+ aChar, aStartOffset, aEndOffset,
+ kOk, kOk, kOk,
+ acc.getTextAfterOffset, "getTextAfterOffset ");
+ }
+}
+
+/**
+ * Test getTextAfterOffset function over different elements
+ *
+ * @param aIDs [in] ID or array of IDs
+ * @param aBoundaryType [in] boundary type for text to be retrieved
+ * @param aTestList [in] array of sets:
+ * offset1 and offset2 defining the offset range
+ * the text in the range
+ * start offset of the text in the range
+ * end offset of the text in the range
+ *
+ * or
+ *
+ * @param aOffset [in] the offset to get the text after
+ * @param aBoundaryType [in] Boundary type for text to be retrieved
+ * @param aText [in] expected return text for getTextAfterOffset
+ * @param aStartOffset [in] expected return start offset for getTextAfterOffset
+ * @param aEndOffset [in] expected return end offset for getTextAfterOffset
+ * @param ... [in] list of ids or list of tuples made of:
+ * element identifier
+ * kTodo or kOk for returned text
+ * kTodo or kOk for returned start offset
+ * kTodo or kOk for returned offset result
+ */
+function testTextAfterOffset(aOffset, aBoundaryType,
+ aText, aStartOffset, aEndOffset)
+{
+ testTextSuperHelper("getTextAfterOffset", arguments);
+}
+
+/**
+ * Test getTextBeforeOffset for BOUNDARY_CHAR over different elements.
+ *
+ * @param aIDs [in] the accessible identifier or array of accessible
+ * identifiers
+ * @param aOffset [in] the offset to get a character before it
+ * @param aChar [in] the expected character
+ * @param aStartOffset [in] expected start offset of the character
+ * @param aEndOffset [in] expected end offset of the character
+ */
+function testCharBeforeOffset(aIDs, aOffset, aChar, aStartOffset, aEndOffset)
+{
+ var IDs = (aIDs instanceof Array) ? aIDs : [ aIDs ];
+ for (var i = 0; i < IDs.length; i++) {
+ var acc = getAccessible(IDs[i], nsIAccessibleText);
+ testTextHelper(IDs[i], aOffset, BOUNDARY_CHAR,
+ aChar, aStartOffset, aEndOffset,
+ kOk, kOk, kOk,
+ acc.getTextBeforeOffset, "getTextBeforeOffset ");
+ }
+}
+
+/**
+ * Test getTextBeforeOffset function over different elements
+ *
+ * @param aIDs [in] ID or array of IDs
+ * @param aBoundaryType [in] boundary type for text to be retrieved
+ * @param aTestList [in] array of sets:
+ * offset1 and offset2 defining the offset range
+ * the text in the range
+ * start offset of the text in the range
+ * end offset of the text in the range
+ *
+ * or
+ *
+ * @param aOffset [in] the offset to get the text before
+ * @param aBoundaryType [in] Boundary type for text to be retrieved
+ * @param aText [in] expected return text for getTextBeforeOffset
+ * @param aStartOffset [in] expected return start offset for getTextBeforeOffset
+ * @param aEndOffset [in] expected return end offset for getTextBeforeOffset
+ * @param ... [in] list of ids or list of tuples made of:
+ * element identifier
+ * kTodo or kOk for returned text
+ * kTodo or kOk for returned start offset
+ * kTodo or kOk for returned offset result
+ */
+function testTextBeforeOffset(aOffset, aBoundaryType,
+ aText, aStartOffset, aEndOffset)
+{
+ testTextSuperHelper("getTextBeforeOffset", arguments);
+}
+
+/**
+ * Test word count for an element.
+ *
+ * @param aElement [in] element identifier
+ * @param aCount [in] Expected word count
+ * @param aToDoFlag [in] kTodo or kOk for returned text
+ */
+function testWordCount(aElement, aCount, aToDoFlag)
+{
+ var isFunc = (aToDoFlag == kTodo) ? todo_is : is;
+ var acc = getAccessible(aElement, nsIAccessibleText);
+ var startOffsetObj = {}, endOffsetObj = {};
+ var length = acc.characterCount;
+ var offset = 0;
+ var wordCount = 0;
+ while (true) {
+ var text = acc.getTextAtOffset(offset, BOUNDARY_WORD_START,
+ startOffsetObj, endOffsetObj);
+ if (offset >= length)
+ break;
+
+ wordCount++;
+ offset = endOffsetObj.value;
+ }
+ isFunc(wordCount, aCount,
+ "wrong words count for '" + acc.getText(0, -1) + "': " + wordCount +
+ " in " + prettyName(aElement));
+}
+
+/**
+ * Test word at a position for an element.
+ *
+ * @param aElement [in] element identifier
+ * @param aWordIndex [in] index of the word to test
+ * @param aText [in] expected text for that word
+ * @param aToDoFlag [in] kTodo or kOk for returned text
+ */
+function testWordAt(aElement, aWordIndex, aText, aToDoFlag)
+{
+ var isFunc = (aToDoFlag == kTodo) ? todo_is : is;
+ var acc = getAccessible(aElement, nsIAccessibleText);
+
+ var textLength = acc.characterCount;
+ var wordIdx = aWordIndex;
+ var startOffsetObj = { value: 0 }, endOffsetObj = { value: 0 };
+ for (offset = 0; offset < textLength; offset = endOffsetObj.value) {
+ acc.getTextAtOffset(offset, BOUNDARY_WORD_START,
+ startOffsetObj, endOffsetObj);
+
+ wordIdx--;
+ if (wordIdx < 0)
+ break;
+ }
+
+ if (wordIdx >= 0) {
+ ok(false,
+ "the given word index '" + aWordIndex + "' exceeds words amount in " +
+ prettyName(aElement));
+
+ return;
+ }
+
+ var startWordOffset = startOffsetObj.value;
+ var endWordOffset = endOffsetObj.value;
+
+ // Calculate the end word offset.
+ acc.getTextAtOffset(endOffsetObj.value, BOUNDARY_WORD_END,
+ startOffsetObj, endOffsetObj);
+ if (startOffsetObj.value != textLength)
+ endWordOffset = startOffsetObj.value
+
+ if (endWordOffset <= startWordOffset) {
+ todo(false,
+ "wrong start and end offset for word at index '" + aWordIndex + "': " +
+ " of text '" + acc.getText(0, -1) + "' in " + prettyName(aElement));
+
+ return;
+ }
+
+ text = acc.getText(startWordOffset, endWordOffset);
+ isFunc(text, aText, "wrong text for word at index '" + aWordIndex + "': " +
+ " of text '" + acc.getText(0, -1) + "' in " + prettyName(aElement));
+}
+
+/**
+ * Test words in a element.
+ *
+ * @param aElement [in] element identifier
+ * @param aWords [in] array of expected words
+ * @param aToDoFlag [in, optional] kTodo or kOk for returned text
+ */
+function testWords(aElement, aWords, aToDoFlag)
+{
+ if (aToDoFlag == null)
+ aToDoFlag = kOk;
+
+ testWordCount(aElement, aWords.length, aToDoFlag);
+
+ for (var i = 0; i < aWords.length; i++) {
+ testWordAt(aElement, i, aWords[i], aToDoFlag);
+ }
+}
+
+/**
+ * Remove all selections.
+ *
+ * @param aID [in] Id, DOM node, or acc obj
+ */
+function cleanTextSelections(aID)
+{
+ var acc = getAccessible(aID, [nsIAccessibleText]);
+
+ while (acc.selectionCount > 0)
+ acc.removeSelection(0);
+}
+
+/**
+ * Test addSelection method.
+ *
+ * @param aID [in] Id, DOM node, or acc obj
+ * @param aStartOffset [in] start offset for the new selection
+ * @param aEndOffset [in] end offset for the new selection
+ * @param aSelectionsCount [in] expected number of selections after addSelection
+ */
+function testTextAddSelection(aID, aStartOffset, aEndOffset, aSelectionsCount)
+{
+ var acc = getAccessible(aID, [nsIAccessibleText]);
+ var text = acc.getText(0, -1);
+
+ acc.addSelection(aStartOffset, aEndOffset);
+
+ ok(acc.selectionCount, aSelectionsCount,
+ text + ": failed to add selection from offset '" + aStartOffset +
+ "' to offset '" + aEndOffset + "': selectionCount after");
+}
+
+/**
+ * Test removeSelection method.
+ *
+ * @param aID [in] Id, DOM node, or acc obj
+ * @param aSelectionIndex [in] index of the selection to be removed
+ * @param aSelectionsCount [in] expected number of selections after
+ * removeSelection
+ */
+function testTextRemoveSelection(aID, aSelectionIndex, aSelectionsCount)
+{
+ var acc = getAccessible(aID, [nsIAccessibleText]);
+ var text = acc.getText(0, -1);
+
+ acc.removeSelection(aSelectionIndex);
+
+ ok(acc.selectionCount, aSelectionsCount,
+ text + ": failed to remove selection at index '" +
+ aSelectionIndex + "': selectionCount after");
+}
+
+/**
+ * Test setSelectionBounds method.
+ *
+ * @param aID [in] Id, DOM node, or acc obj
+ * @param aStartOffset [in] new start offset for the selection
+ * @param aEndOffset [in] new end offset for the selection
+ * @param aSelectionIndex [in] index of the selection to set
+ * @param aSelectionsCount [in] expected number of selections after
+ * setSelectionBounds
+ */
+function testTextSetSelection(aID, aStartOffset, aEndOffset,
+ aSelectionIndex, aSelectionsCount)
+{
+ var acc = getAccessible(aID, [nsIAccessibleText]);
+ var text = acc.getText(0, -1);
+
+ acc.setSelectionBounds(aSelectionIndex, aStartOffset, aEndOffset);
+
+ is(acc.selectionCount, aSelectionsCount,
+ text + ": failed to set selection at index '" +
+ aSelectionIndex + "': selectionCount after");
+}
+
+/**
+ * Test selectionCount method.
+ *
+ * @param aID [in] Id, DOM node, or acc obj
+ * @param aCount [in] expected selection count
+ */
+function testTextSelectionCount(aID, aCount)
+{
+ var acc = getAccessible(aID, [nsIAccessibleText]);
+ var text = acc.getText(0, -1);
+
+ is(acc.selectionCount, aCount, text + ": wrong selectionCount: ");
+}
+
+/**
+ * Test getSelectionBounds method.
+ *
+ * @param aID [in] Id, DOM node, or acc obj
+ * @param aStartOffset [in] expected start offset for the selection
+ * @param aEndOffset [in] expected end offset for the selection
+ * @param aSelectionIndex [in] index of the selection to get
+ */
+function testTextGetSelection(aID, aStartOffset, aEndOffset, aSelectionIndex)
+{
+ var acc = getAccessible(aID, [nsIAccessibleText]);
+ var text = acc.getText(0, -1);
+
+ var startObj = {}, endObj = {};
+ acc.getSelectionBounds(aSelectionIndex, startObj, endObj);
+
+ is(startObj.value, aStartOffset, text + ": wrong start offset for index '" +
+ aSelectionIndex + "'");
+ is(endObj.value, aEndOffset, text + ": wrong end offset for index '" +
+ aSelectionIndex + "'");
+}
+
+function testTextRange(aRange, aRangeDescr, aStartContainer, aStartOffset,
+ aEndContainer, aEndOffset, aText,
+ aCommonContainer, aChildren)
+{
+ isObject(aRange.startContainer, getAccessible(aStartContainer),
+ "Wrong start container of " + aRangeDescr);
+ is(aRange.startOffset, aStartOffset,
+ "Wrong start offset of " + aRangeDescr);
+ isObject(aRange.endContainer, getAccessible(aEndContainer),
+ "Wrong end container of " + aRangeDescr);
+ is(aRange.endOffset, aEndOffset,
+ "Wrong end offset of " + aRangeDescr);
+
+ if (aText === undefined) {
+ return;
+ }
+
+ is(aRange.text, aText, "Wrong text of " + aRangeDescr);
+
+ var children = aRange.embeddedChildren;
+ is(children ? children.length : 0, aChildren ? aChildren.length : 0,
+ "Wrong embedded children count of " + aRangeDescr);
+
+ isObject(aRange.container, getAccessible(aCommonContainer),
+ "Wrong container of " + aRangeDescr);
+
+ if (aChildren) {
+ for (var i = 0; i < aChildren.length; i++) {
+ var expectedChild = getAccessible(aChildren[i]);
+ var actualChild = children.queryElementAt(i, nsIAccessible);
+ isObject(actualChild, expectedChild,
+ "Wrong child at index '" + i + "' of " + aRangeDescr);
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Private
+
+function testTextSuperHelper(aFuncName, aArgs)
+{
+ // List of tests.
+ if (aArgs[2] instanceof Array) {
+ var ids = (aArgs[0] instanceof Array) ? aArgs[0] : [ aArgs[0] ];
+ var boundaryType = aArgs[1];
+ var list = aArgs[2];
+ for (var i = 0; i < list.length; i++) {
+ var offset1 = list[i][0], offset2 = list[i][1];
+ var text = list[i][2], startOffset = list[i][3], endOffset = list[i][4];
+ var failureList = list[i][5];
+ for (var offset = offset1; offset <= offset2; offset++) {
+ for (var idIdx = 0; idIdx < ids.length; idIdx++) {
+ var id = ids[idIdx];
+
+ var flagOk1 = kOk, flagOk2 = kOk, flagOk3 = kOk;
+ if (failureList) {
+ for (var fIdx = 0; fIdx < failureList.length; fIdx++) {
+ if (offset == failureList[fIdx][0] && id == failureList[fIdx][1]) {
+ flagOk1 = failureList[fIdx][2];
+ flagOk2 = failureList[fIdx][3];
+ flagOk3 = failureList[fIdx][4];
+ break;
+ }
+ }
+ }
+
+ var acc = getAccessible(id, nsIAccessibleText);
+ testTextHelper(id, offset, boundaryType,
+ text, startOffset, endOffset,
+ flagOk1, flagOk2, flagOk3,
+ acc[aFuncName], aFuncName + " ");
+ }
+ }
+ }
+ return;
+ }
+
+ // Test at single offset. List of IDs.
+ var offset = aArgs[0];
+ var boundaryType = aArgs[1];
+ var text = aArgs[2];
+ var startOffset = aArgs[3];
+ var endOffset = aArgs[4];
+ if (aArgs[5] instanceof Array) {
+ var ids = aArgs[5];
+ for (var i = 0; i < ids.length; i++) {
+ var acc = getAccessible(ids[i], nsIAccessibleText);
+ testTextHelper(ids[i], offset, boundaryType,
+ text, startOffset, endOffset,
+ kOk, kOk, kOk,
+ acc[aFuncName], aFuncName + " ");
+ }
+
+ return;
+ }
+
+ // Each ID is tested separately.
+ for (var i = 5; i < aArgs.length; i = i + 4) {
+ var ID = aArgs[i];
+ var acc = getAccessible(ID, nsIAccessibleText);
+ var toDoFlag1 = aArgs[i + 1];
+ var toDoFlag2 = aArgs[i + 2];
+ var toDoFlag3 = aArgs[i + 3];
+
+ testTextHelper(ID, offset, boundaryType,
+ text, startOffset, endOffset,
+ toDoFlag1, toDoFlag2, toDoFlag3,
+ acc[aFuncName], aFuncName + " ");
+ }
+}
+
+function testTextHelper(aID, aOffset, aBoundaryType,
+ aText, aStartOffset, aEndOffset,
+ aToDoFlag1, aToDoFlag2, aToDoFlag3,
+ aTextFunc, aTextFuncName)
+{
+ var exceptionFlag = aToDoFlag1 == undefined ||
+ aToDoFlag2 == undefined ||
+ aToDoFlag3 == undefined;
+
+ var startMsg = aTextFuncName + "(" + boundaryToString(aBoundaryType) + "): ";
+ var endMsg = ", id: " + prettyName(aID) + ";";
+
+ try {
+ var startOffsetObj = {}, endOffsetObj = {};
+ var text = aTextFunc(aOffset, aBoundaryType,
+ startOffsetObj, endOffsetObj);
+
+ if (exceptionFlag) {
+ ok(false, startMsg + "no expected failure at offset " + aOffset + endMsg);
+ return;
+ }
+
+ var isFunc1 = (aToDoFlag1 == kTodo) ? todo : ok;
+ var isFunc2 = (aToDoFlag2 == kTodo) ? todo : ok;
+ var isFunc3 = (aToDoFlag3 == kTodo) ? todo : ok;
+
+ isFunc1(text == aText,
+ startMsg + "wrong text " +
+ "(got '" + text + "', expected: '" + aText + "')" +
+ ", offset: " + aOffset + endMsg);
+ isFunc2(startOffsetObj.value == aStartOffset,
+ startMsg + "wrong start offset" +
+ "(got '" + startOffsetObj.value + "', expected: '" + aStartOffset + "')" +
+ ", offset: " + aOffset + endMsg);
+ isFunc3(endOffsetObj.value == aEndOffset,
+ startMsg + "wrong end offset" +
+ "(got '" + endOffsetObj.value + "', expected: '" + aEndOffset + "')" +
+ ", offset: " + aOffset + endMsg);
+
+ } catch (e) {
+ var okFunc = exceptionFlag ? todo : ok;
+ okFunc(false, startMsg + "failed at offset " + aOffset + endMsg +
+ ", exception: " + e);
+ }
+}
+
+function boundaryToString(aBoundaryType)
+{
+ switch (aBoundaryType) {
+ case BOUNDARY_CHAR:
+ return "char";
+ case BOUNDARY_WORD_START:
+ return "word start";
+ case BOUNDARY_WORD_END:
+ return "word end";
+ case BOUNDARY_LINE_START:
+ return "line start";
+ case BOUNDARY_LINE_END:
+ return "line end";
+ }
+}
diff --git a/accessible/tests/mochitest/text/a11y.ini b/accessible/tests/mochitest/text/a11y.ini
new file mode 100644
index 000000000..96283a736
--- /dev/null
+++ b/accessible/tests/mochitest/text/a11y.ini
@@ -0,0 +1,16 @@
+[DEFAULT]
+support-files = doc.html
+ !/accessible/tests/mochitest/*.js
+
+[test_atcaretoffset.html]
+[test_charboundary.html]
+[test_doc.html]
+[test_dynamic.html]
+[test_general.xul]
+[test_gettext.html]
+[test_hypertext.html]
+[test_lineboundary.html]
+[test_passwords.html]
+[test_selection.html]
+[test_wordboundary.html]
+[test_words.html]
diff --git a/accessible/tests/mochitest/text/doc.html b/accessible/tests/mochitest/text/doc.html
new file mode 100644
index 000000000..d57406c22
--- /dev/null
+++ b/accessible/tests/mochitest/text/doc.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script type="application/javascript">
+ document.documentElement.appendChild(document.createTextNode("outbody"));
+ </script>
+</head>
+<body>inbody</body>
+</html>
diff --git a/accessible/tests/mochitest/text/test_atcaretoffset.html b/accessible/tests/mochitest/text/test_atcaretoffset.html
new file mode 100644
index 000000000..330298a62
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_atcaretoffset.html
@@ -0,0 +1,455 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test: nsIAccessibleText getText* functions at caret offset</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/MochiKit/packed.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+
+ <script type="application/javascript">
+ //gA11yEventDumpToConsole = true; // debugging
+
+ function traverseTextByLines(aQueue, aID, aLines)
+ {
+ var wholeText = "";
+ for (var i = 0; i < aLines.length ; i++)
+ wholeText += aLines[i][0] + aLines[i][1];
+
+ var baseInvokerFunc = synthClick;
+ var charIter = new charIterator(wholeText, aLines);
+ //charIter.debugOffset = 10; // enable to run tests at given offset only
+
+ while (charIter.next()) {
+ aQueue.push(new tmpl_moveTo(aID, baseInvokerFunc, wholeText, charIter));
+ baseInvokerFunc = synthRightKey;
+ }
+ }
+
+ /**
+ * Used to get test list for each traversed character.
+ */
+ function charIterator(aWholeText, aLines)
+ {
+ this.next = function charIterator_next()
+ {
+ // Don't increment offset if we are at end of the wrapped line
+ // (offset is shared between end of this line and start of next line).
+ if (this.mAtWrappedLineEnd) {
+ this.mAtWrappedLineEnd = false;
+ this.mLine = this.mLine.nextLine;
+ return true;
+ }
+
+ this.mOffset++;
+ if (this.mOffset > aWholeText.length)
+ return false;
+
+ var nextLine = this.mLine.nextLine;
+ if (!nextLine.isFakeLine() && this.mOffset == nextLine.start) {
+ if (nextLine.start == this.mLine.end)
+ this.mAtWrappedLineEnd = true;
+ else
+ this.mLine = nextLine;
+ }
+
+ return true;
+ }
+
+ Object.defineProperty(this, "offset", { get: function()
+ { return this.mOffset; }
+ });
+
+ Object.defineProperty(this, "offsetDescr", { get: function()
+ {
+ return this.mOffset + " offset (" + this.mLine.number + " line, " +
+ (this.mOffset - this.mLine.start) + " offset on the line)";
+ }
+ });
+
+ Object.defineProperty(this, "tests", { get: function()
+ {
+ // Line boundary tests.
+ var cLine = this.mLine;
+ var pLine = cLine.prevLine;
+ var ppLine = pLine.prevLine;
+ var nLine = cLine.nextLine;
+ var nnLine = nLine.nextLine;
+
+ var lineTests = [
+ [ testTextBeforeOffset, BOUNDARY_LINE_START, pLine.start, cLine.start],
+ [ testTextBeforeOffset, BOUNDARY_LINE_END, ppLine.end, pLine.end],
+ [ testTextAtOffset, BOUNDARY_LINE_START, cLine.start, nLine.start],
+ [ testTextAtOffset, BOUNDARY_LINE_END, pLine.end, cLine.end],
+ [ testTextAfterOffset, BOUNDARY_LINE_START, nLine.start, nnLine.start],
+ [ testTextAfterOffset, BOUNDARY_LINE_END, cLine.end, nLine.end]
+ ];
+
+ // Word boundary tests.
+ var cWord = this.mLine.firstWord;
+ var nWord = cWord.nextWord, pWord = cWord.prevWord;
+
+ // The current word is a farthest word starting at or after the offset.
+ if (this.mOffset >= nWord.start) {
+ while (this.mOffset >= nWord.start && !this.mLine.isLastWord(cWord)) {
+ cWord = nWord;
+ nWord = nWord.nextWord;
+ }
+ pWord = cWord.prevWord;
+
+ } else if (this.mOffset < cWord.start) {
+ while (this.mOffset < cWord.start) {
+ cWord = pWord;
+ pWord = pWord.prevWord;
+ }
+ nWord = cWord.nextWord;
+ }
+
+ var nnWord = nWord.nextWord, ppWord = pWord.prevWord;
+
+ var isAfterWordEnd =
+ this.mOffset > cWord.end || cWord.line != this.mLine;
+ var isAtOrAfterWordEnd = (this.mOffset >= cWord.end);
+ var useNextWordForAtWordEnd =
+ isAtOrAfterWordEnd && this.mOffset != aWholeText.length;
+
+ var wordTests = [
+ [ testTextBeforeOffset, BOUNDARY_WORD_START,
+ pWord.start, cWord.start ],
+ [ testTextBeforeOffset, BOUNDARY_WORD_END,
+ (isAfterWordEnd ? pWord : ppWord).end,
+ (isAfterWordEnd ? cWord : pWord).end ],
+ [ testTextAtOffset, BOUNDARY_WORD_START,
+ cWord.start, nWord.start ],
+ [ testTextAtOffset, BOUNDARY_WORD_END,
+ (useNextWordForAtWordEnd ? cWord : pWord).end,
+ (useNextWordForAtWordEnd ? nWord : cWord).end ],
+ [ testTextAfterOffset, BOUNDARY_WORD_START,
+ nWord.start, nnWord.start ],
+ [ testTextAfterOffset, BOUNDARY_WORD_END,
+ (isAfterWordEnd ? nWord : cWord).end,
+ (isAfterWordEnd ? nnWord : nWord).end ]
+ ];
+
+ // Character boundary tests.
+ var prevOffset = this.offset > 1 ? this.offset - 1 : 0;
+ var nextOffset = this.offset >= aWholeText.length ?
+ this.offset : this.offset + 1;
+ var nextAfterNextOffset = nextOffset >= aWholeText.length ?
+ nextOffset : nextOffset + 1;
+
+ var charTests = [
+ [ testTextBeforeOffset, BOUNDARY_CHAR,
+ prevOffset, this.offset ],
+ [ testTextAtOffset, BOUNDARY_CHAR,
+ this.offset,
+ this.mAtWrappedLineEnd ? this.offset : nextOffset ],
+ [ testTextAfterOffset, BOUNDARY_CHAR,
+ this.mAtWrappedLineEnd ? this.offset : nextOffset,
+ this.mAtWrappedLineEnd ? nextOffset : nextAfterNextOffset ]
+ ];
+
+ return lineTests.concat(wordTests.concat(charTests));
+ }
+ });
+
+ Object.defineProperty(this, "failures", { get: function()
+ {
+ if (this.mOffset == this.mLine.start)
+ return this.mLine.lineStartFailures;
+ if (this.mOffset == this.mLine.end)
+ return this.mLine.lineEndFailures;
+ return [];
+ }
+ });
+
+ this.mOffset = -1;
+ this.mLine = new line(aWholeText, aLines, 0);
+ this.mAtWrappedLineEnd = false;
+ this.mWord = this.mLine.firstWord;
+ }
+
+ /**
+ * A line object. Allows to navigate by lines and by words.
+ */
+ function line(aWholeText, aLines, aIndex)
+ {
+ Object.defineProperty(this, "prevLine", { get: function()
+ {
+ return new line(aWholeText, aLines, aIndex - 1);
+ }
+ });
+ Object.defineProperty(this, "nextLine", { get: function()
+ {
+ return new line(aWholeText, aLines, aIndex + 1);
+ }
+ });
+
+ Object.defineProperty(this, "start", { get: function()
+ {
+ if (aIndex < 0)
+ return 0;
+
+ if (aIndex >= aLines.length)
+ return aWholeText.length;
+
+ return aLines[aIndex][2];
+ }
+ });
+ Object.defineProperty(this, "end", { get: function()
+ {
+ if (aIndex < 0)
+ return 0;
+
+ if (aIndex >= aLines.length)
+ return aWholeText.length;
+
+ return aLines[aIndex][3];
+ }
+ });
+
+ Object.defineProperty(this, "number", { get: function()
+ { return aIndex; }
+ });
+ Object.defineProperty(this, "wholeText", { get: function()
+ { return aWholeText; }
+ });
+ this.isFakeLine = function line_isFakeLine()
+ {
+ return aIndex < 0 || aIndex >= aLines.length;
+ }
+
+ Object.defineProperty(this, "lastWord", { get: function()
+ {
+ if (aIndex < 0)
+ return new word(this, [], -1);
+ if (aIndex >= aLines.length)
+ return new word(this, [], 0);
+
+ var words = aLines[aIndex][4].words;
+ return new word(this, words, words.length - 2);
+ }
+ });
+ Object.defineProperty(this, "firstWord", { get: function()
+ {
+ if (aIndex < 0)
+ return new word(this, [], -1);
+ if (aIndex >= aLines.length)
+ return new word(this, [], 0);
+
+ var words = aLines[aIndex][4].words;
+ return new word(this, words, 0);
+ }
+ });
+
+ this.isLastWord = function line_isLastWord(aWord)
+ {
+ var lastWord = this.lastWord;
+ return lastWord.start == aWord.start && lastWord.end == aWord.end;
+ }
+
+ Object.defineProperty(this, "lineStartFailures", { get: function()
+ {
+ if (aIndex < 0 || aIndex >= aLines.length)
+ return [];
+
+ return aLines[aIndex][4].lsf || [];
+ }
+ });
+ Object.defineProperty(this, "lineEndFailures", { get: function()
+ {
+ if (aIndex < 0 || aIndex >= aLines.length)
+ return [];
+
+ return aLines[aIndex][4].lef || [];
+ }
+ });
+ }
+
+ /**
+ * A word object. Allows to navigate by words.
+ */
+ function word(aLine, aWords, aIndex)
+ {
+ Object.defineProperty(this, "prevWord", { get: function()
+ {
+ if (aIndex >= 2)
+ return new word(aLine, aWords, aIndex - 2);
+
+ var prevLineLastWord = aLine.prevLine.lastWord;
+ if (this.start == prevLineLastWord.start && !this.isFakeStartWord())
+ return prevLineLastWord.prevWord;
+ return prevLineLastWord;
+ }
+ });
+ Object.defineProperty(this, "nextWord", { get: function()
+ {
+ if (aIndex + 2 < aWords.length)
+ return new word(aLine, aWords, aIndex + 2);
+
+ var nextLineFirstWord = aLine.nextLine.firstWord;
+ if (this.end == nextLineFirstWord.end && !this.isFakeEndWord())
+ return nextLineFirstWord.nextWord;
+ return nextLineFirstWord;
+ }
+ });
+
+ Object.defineProperty(this, "line", { get: function() { return aLine; } });
+
+ Object.defineProperty(this, "start", { get: function()
+ {
+ if (this.isFakeStartWord())
+ return 0;
+
+ if (this.isFakeEndWord())
+ return aLine.end;
+ return aWords[aIndex];
+ }
+ });
+ Object.defineProperty(this, "end", { get: function()
+ {
+ if (this.isFakeStartWord())
+ return 0;
+
+ return this.isFakeEndWord() ? aLine.end : aWords[aIndex + 1];
+ }
+ });
+
+ this.toString = function word_toString()
+ {
+ var start = this.start, end = this.end;
+ return "'" + aLine.wholeText.substring(start, end) +
+ "' at [" + start + ", " + end + "]";
+ }
+
+ this.isFakeStartWord = function() { return aIndex < 0; }
+ this.isFakeEndWord = function() { return aIndex >= aWords.length; }
+ }
+
+ /**
+ * A template invoker to move through the text.
+ */
+ function tmpl_moveTo(aID, aInvokerFunc, aWholeText, aCharIter)
+ {
+ this.offset = aCharIter.offset;
+
+ var checker = new caretMoveChecker(this.offset, aID);
+ this.__proto__ = new (aInvokerFunc)(aID, checker);
+
+ this.finalCheck = function genericMoveTo_finalCheck()
+ {
+ if (this.noTests())
+ return;
+
+ for (var i = 0; i < this.tests.length; i++) {
+ var func = this.tests[i][0];
+ var boundary = this.tests[i][1];
+ var startOffset = this.tests[i][2];
+ var endOffset = this.tests[i][3];
+ var text = aWholeText.substring(startOffset, endOffset);
+
+ var isOk1 = kOk, isOk2 = kOk, isOk3 = kOk;
+ for (var fIdx = 0; fIdx < this.failures.length; fIdx++) {
+ var failure = this.failures[fIdx];
+ if (func.name.indexOf(failure[0]) != -1 && boundary == failure[1]) {
+ isOk1 = failure[2];
+ isOk2 = failure[3];
+ isOk3 = failure[4];
+ }
+ }
+
+ func.call(null, kCaretOffset, boundary, text, startOffset, endOffset,
+ aID, isOk1, isOk2, isOk3);
+ }
+ }
+
+ this.getID = function genericMoveTo_getID()
+ {
+ return "move to " + this.offsetDescr;
+ }
+
+ this.noTests = function tmpl_moveTo_noTests()
+ {
+ return ("debugOffset" in aCharIter) &&
+ (aCharIter.debugOffset != this.offset);
+ }
+
+ this.offsetDescr = aCharIter.offsetDescr;
+ this.tests = this.noTests() ? null : aCharIter.tests;
+ this.failures = aCharIter.failures;
+ }
+
+ var gQueue = null;
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ // __a__w__o__r__d__\n
+ // 0 1 2 3 4 5
+ // __t__w__o__ (soft line break)
+ // 6 7 8 9
+ // __w__o__r__d__s
+ // 10 11 12 13 14 15
+
+ traverseTextByLines(gQueue, "textarea",
+ [ [ "aword", "\n", 0, 5, { words: [ 0, 5 ] } ],
+ [ "two ", "", 6, 10, { words: [ 6, 9 ] } ],
+ [ "words", "", 10, 15, { words: [ 10, 15 ] } ]
+ ] );
+
+ var line4 = [ // "riend "
+ [ "TextBeforeOffset", BOUNDARY_WORD_END, kTodo, kTodo, kTodo ],
+ [ "TextAfterOffset", BOUNDARY_WORD_END, kTodo, kTodo, kTodo ]
+ ];
+ traverseTextByLines(gQueue, "ta_wrapped",
+ [ [ "hi ", "", 0, 3, { words: [ 0, 2 ] } ],
+ [ "hello ", "", 3, 9, { words: [ 3, 8 ] } ],
+ [ "my ", "", 9, 12, { words: [ 9, 11 ] } ],
+ [ "longf", "", 12, 17, { words: [ 12, 17 ] } ],
+ [ "riend ", "", 17, 23, { words: [ 17, 22 ], lsf: line4 } ],
+ [ "t sq ", "", 23, 28, { words: [ 23, 24, 25, 27 ] } ],
+ [ "t", "", 28, 29, { words: [ 28, 29 ] } ]
+ ] );
+
+ gQueue.invoke(); // will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="nsIAccessibleText getText related functions tests at caret offset"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=852021">
+ Bug 852021
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+
+ <textarea id="textarea" cols="5">aword
+two words</textarea>
+
+ <textarea id="ta_wrapped" cols="5">hi hello my longfriend t sq t</textarea>
+ </pre>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/text/test_charboundary.html b/accessible/tests/mochitest/text/test_charboundary.html
new file mode 100644
index 000000000..2fddcb5be
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_charboundary.html
@@ -0,0 +1,140 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Char boundary text tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ //
+ // __h__e__l__l__o__ __m__y__ __f__r__i__e__n__d__
+ // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+
+ var IDs = [ "i1", "d1", "e1", "t1" ];
+
+ testCharBeforeOffset(IDs, 0, "", 0, 0);
+ testCharBeforeOffset(IDs, 1, "h", 0, 1);
+ testCharBeforeOffset(IDs, 14, "n", 13, 14);
+ testCharBeforeOffset(IDs, 15, "d", 14, 15);
+
+ testCharAtOffset(IDs, 0, "h", 0, 1);
+ testCharAtOffset(IDs, 1, "e", 1, 2);
+ testCharAtOffset(IDs, 14, "d", 14, 15);
+ testCharAtOffset(IDs, 15, "", 15, 15);
+
+ testCharAfterOffset(IDs, 0, "e", 1, 2);
+ testCharAfterOffset(IDs, 1, "l", 2, 3);
+ testCharAfterOffset(IDs, 14, "", 15, 15);
+ testCharAfterOffset(IDs, 15, "", 15, 15);
+
+ //////////////////////////////////////////////////////////////////////////
+ //
+ // __B__r__a__v__e__ __S__i__r__ __ __R__o__b__i__n__ __ __ __r__a__n
+ // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
+
+ IDs = [ "i2", "d2", "e2", "t2" ];
+
+ testCharBeforeOffset(IDs, 0, "", 0, 0);
+ testCharBeforeOffset(IDs, 1, "B", 0, 1);
+ testCharBeforeOffset(IDs, 6, " ", 5, 6);
+ testCharBeforeOffset(IDs, 10, " ", 9, 10);
+ testCharBeforeOffset(IDs, 11, " ", 10, 11);
+ testCharBeforeOffset(IDs, 17, " ", 16, 17);
+ testCharBeforeOffset(IDs, 19, " ", 18, 19);
+
+ testCharAtOffset(IDs, 0, "B", 0, 1);
+ testCharAtOffset(IDs, 1, "r", 1, 2);
+ testCharAtOffset(IDs, 5, " ", 5, 6);
+ testCharAtOffset(IDs, 9, " ", 9, 10);
+ testCharAtOffset(IDs, 10, " ", 10, 11);
+ testCharAtOffset(IDs, 17, " ", 17, 18);
+
+ testCharAfterOffset(IDs, 0, "r", 1, 2);
+ testCharAfterOffset(IDs, 1, "a", 2, 3);
+ testCharAfterOffset(IDs, 4, " ", 5, 6);
+ testCharAfterOffset(IDs, 5, "S", 6, 7);
+ testCharAfterOffset(IDs, 8, " ", 9, 10);
+ testCharAfterOffset(IDs, 9, " ", 10, 11);
+ testCharAfterOffset(IDs, 10, "R", 11, 12);
+ testCharAfterOffset(IDs, 15, " ", 16, 17);
+ testCharAfterOffset(IDs, 16, " ", 17, 18);
+ testCharAfterOffset(IDs, 17, " ", 18, 19);
+ testCharAfterOffset(IDs, 18, "r", 19, 20);
+
+ //////////////////////////////////////////////////////////////////////////
+ //
+ // __o__n__e__w__o__r__d__\n
+ // 0 1 2 3 4 5 6 7
+ // __\n
+ // 8
+ // __t__w__o__ __w__o__r__d__s__\n
+ // 9 10 11 12 13 14 15 16 17 18
+
+ IDs = ["d3", "dbr3", "e3", "ebr3", "t3"];
+
+ testCharBeforeOffset(IDs, 8, "\n", 7, 8);
+ testCharBeforeOffset(IDs, 9, "\n", 8, 9);
+ testCharBeforeOffset(IDs, 10, "t", 9, 10);
+
+ testCharAtOffset(IDs, 7, "\n", 7, 8);
+ testCharAtOffset(IDs, 8, "\n", 8, 9);
+ testCharAtOffset(IDs, 9, "t", 9, 10);
+
+ testCharAfterOffset(IDs, 6, "\n", 7, 8);
+ testCharAfterOffset(IDs, 7, "\n", 8, 9);
+ testCharAfterOffset(IDs, 8, "t", 9, 10);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="i1" value="hello my friend"/>
+ <div id="d1">hello my friend</div>
+ <div id="e1" contenteditable="true">hello my friend</div>
+ <textarea id="t1" contenteditable="true">hello my friend</textarea>
+
+ <input id="i2" value="Brave Sir Robin ran"/>
+ <pre>
+ <div id="d2">Brave Sir Robin ran</div>
+ <div id="e2" contenteditable="true">Brave Sir Robin ran</div>
+ </pre>
+ <textarea id="t2" cols="300">Brave Sir Robin ran</textarea>
+
+ <pre>
+ <div id="d3">oneword
+
+two words
+</div>
+ <div id="dbr3">oneword<br/><br/>two words<br/></div>
+ <div id="e3" contenteditable="true">oneword
+
+two words
+</div>
+ <div id="ebr3" contenteditable="true">oneword<br/><br/>two words<br/></div>
+ <textarea id="t3" cols="300">oneword
+
+two words</textarea>
+ </pre>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/text/test_doc.html b/accessible/tests/mochitest/text/test_doc.html
new file mode 100644
index 000000000..9c6788275
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_doc.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>nsIAccessibleText getText related function tests for document accessible</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+ <script type="application/javascript">
+
+ function doTest()
+ {
+ var iframeDoc = [ getNode("iframe").contentDocument ];
+ testCharacterCount(iframeDoc, 15);
+ testText(iframeDoc, 0, 15, "outbody inbody ");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Elements appended outside the body aren't accessible"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=608887">Mozilla Bug 608887</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <iframe id="iframe" src="doc.html"></iframe>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/text/test_dynamic.html b/accessible/tests/mochitest/text/test_dynamic.html
new file mode 100644
index 000000000..0e5d4394a
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_dynamic.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>nsIAccessibleText getText related function tests for tree mutations</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function insertBefore(aId, aEl, aTextBefore, aTextAfter, aStartIdx, aEndIdx)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, aId)
+ ];
+
+ this.invoke = function insertBefore_invoke()
+ {
+ testText(aId, 0, -1, aTextBefore);
+ getNode(aId).insertBefore(aEl, getNode(aId).firstChild);
+ }
+
+ this.finalCheck = function insertBefore_finalCheck()
+ {
+ testText(aId, aStartIdx, aEndIdx, aTextAfter);
+ }
+
+ this.getID = function insertTextBefore_getID() {
+ return "insert " + prettyName(aEl) + " before";
+ }
+ }
+
+ function insertTextBefore(aId, aTextBefore, aText)
+ {
+ var el = document.createTextNode(aText);
+ this.__proto__ = new insertBefore(aId, el, aTextBefore,
+ aText + aTextBefore, 0, -1)
+ }
+
+ function insertImgBefore(aId, aTextBefore)
+ {
+ var el = document.createElement("img");
+ el.setAttribute("src", "../moz.png");
+ el.setAttribute("alt", "mozilla");
+
+ this.__proto__ = new insertBefore(aId, el, aTextBefore,
+ kEmbedChar + aTextBefore, 0, -1)
+ }
+
+ function insertTextBefore2(aId)
+ {
+ var el = document.createTextNode("hehe");
+ this.__proto__ = new insertBefore(aId, el, "ho", "ho", 4, -1)
+ }
+
+ var gQueue = null;
+ function doTest()
+ {
+ gQueue = new eventQueue();
+ gQueue.push(new insertTextBefore("c1", "ho", "ha"));
+ gQueue.push(new insertImgBefore("c1", "haho"));
+ gQueue.push(new insertImgBefore("c2", kEmbedChar));
+ gQueue.push(new insertTextBefore2("c3"));
+ gQueue.invoke(); // will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="c1">ho</div>
+ <div id="c2"><img src="../moz.png" alt="mozilla"></div>
+ <div id="c3">ho</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/text/test_general.xul b/accessible/tests/mochitest/text/test_general.xul
new file mode 100644
index 000000000..0bd720e28
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_general.xul
@@ -0,0 +1,80 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Tests: XUL label text interface">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Testing
+
+ var gQueue = null;
+ function doTests()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // XUL label
+
+ var ids = ["label1", "label2"];
+
+ testCharacterCount(ids, 5);
+
+ testText(ids, 0, -1, "Hello");
+ testText(ids, 0, 1, "H");
+
+ testCharAfterOffset(ids, 0, "e", 1, 2);
+ testCharBeforeOffset(ids, 1, "H", 0, 1);
+ testCharAtOffset(ids, 1, "e", 1, 2);
+
+ //////////////////////////////////////////////////////////////////////////
+ // XUL textbox
+
+ testTextAtOffset([ getNode("tbox1").inputField ], BOUNDARY_LINE_START,
+ [ [ 0, 4, "test", 0, 4 ] ]);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ ]]>
+ </script>
+
+ <vbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=396166"
+ title="xul:label@value accessible should implement nsIAccessibleText">
+ Bug 396166
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=899433"
+ title="Accessibility returns empty line for last line in certain cases">
+ Bug 899433
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+ <label id="label1" value="Hello"/>
+ <label id="label2">Hello</label>
+
+ <textbox id="tbox1" value="test" multiline="true"/>
+ </vbox>
+</window>
diff --git a/accessible/tests/mochitest/text/test_gettext.html b/accessible/tests/mochitest/text/test_gettext.html
new file mode 100644
index 000000000..303edc58a
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_gettext.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Get text between offsets tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ //
+ // __h__e__l__l__o__ __m__y__ __f__r__i__e__n__d__
+ // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+
+ var IDs = [ "i1", "d1", "e1", "t1" ];
+
+ testCharacterCount(IDs, 15);
+
+ testText(IDs, 0, 1, "h");
+ testText(IDs, 1, 3, "el");
+ testText(IDs, 14, 15, "d");
+ testText(IDs, 0, 15, "hello my friend");
+ testText(IDs, 0, -1, "hello my friend");
+
+ //////////////////////////////////////////////////////////////////////////
+ //
+ // __B__r__a__v__e__ __S__i__r__ __ __R__o__b__i__n__ __ __ __r__a__n
+ // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
+
+ IDs = [ "i2", "dpre2", "epre2", "t2" ];
+
+ testCharacterCount(IDs, 22);
+
+ testText(IDs, 0, 1, "B");
+ testText(IDs, 5, 6, " ");
+ testText(IDs, 9, 11, " ");
+ testText(IDs, 16, 19, " ");
+ testText(IDs, 0, 22, "Brave Sir Robin ran");
+ testText(IDs, 0, -1, "Brave Sir Robin ran");
+
+ testCharacterCount(["d2", "e2"], 19);
+ testText(["d2", "e2"], 0, 19, "Brave Sir Robin ran");
+
+ //////////////////////////////////////////////////////////////////////////
+ //
+ // __o__n__e__w__o__r__d__\n
+ // 0 1 2 3 4 5 6 7
+ // __\n
+ // 8
+ // __t__w__o__ __w__o__r__d__s__\n
+ // 9 10 11 12 13 14 15 16 17 18
+
+ var IDs = ["d3", "dbr3", "e3", "ebr3", "t3"];
+
+ testCharacterCount(IDs, 19);
+
+ testText(IDs, 0, 19, "oneword\n\ntwo words\n");
+ testText(IDs, 0, -1, "oneword\n\ntwo words\n");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="i1" value="hello my friend"/>
+ <div id="d1">hello my friend</div>
+ <div id="e1" contenteditable="true">hello my friend</div>
+ <textarea id="t1">hello my friend</textarea>
+
+ <input id="i2" value="Brave Sir Robin ran"/>
+ <pre><div id="dpre2">Brave Sir Robin ran</div></pre>
+ <pre><div id="epre2" contenteditable="true">Brave Sir Robin ran</div></pre>
+ <textarea id="t2" cols="300">Brave Sir Robin ran</textarea>
+ <div id="d2">Brave Sir Robin ran</div>
+ <div id="e2" contenteditable="true">Brave Sir Robin ran</div>
+
+ <pre>
+ <div id="d3">oneword
+
+two words
+</div>
+ <div id="dbr3">oneword<br/><br/>two words<br/><br/></div>
+ <div id="e3" contenteditable="true">oneword
+
+two words
+</div>
+ <div id="ebr3" contenteditable="true">oneword<br/><br/>two words<br/><br/></div>
+ <textarea id="t3" cols="300">oneword
+
+two words
+</textarea>
+ </pre>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/text/test_hypertext.html b/accessible/tests/mochitest/text/test_hypertext.html
new file mode 100644
index 000000000..2d71e11a9
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_hypertext.html
@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>nsIAccessibleText getText related function tests for rich text</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <style>
+ #listitemnone {
+ list-style-type: none;
+ }
+ h6.gencontent:before {
+ content: "aga"
+ }
+ </style>
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // null getText
+ //////////////////////////////////////////////////////////////////////////
+
+ var emptyTextAcc = getAccessible("nulltext", [nsIAccessibleText]);
+ is(emptyTextAcc.getText(0, -1), "", "getText() END_OF_TEXT with null string");
+ is(emptyTextAcc.getText(0, 0), "", "getText() Len==0 with null string");
+
+ //////////////////////////////////////////////////////////////////////////
+ // hypertext
+ //////////////////////////////////////////////////////////////////////////
+
+ // ! - embedded object char
+ // __h__e__l__l__o__ __!__ __s__e__e__ __!__
+ // 0 1 2 3 4 5 6 7 8 9 10 11 12 13
+
+ var IDs = [ "hypertext", "hypertext2" ];
+
+ ////////////////////////////////////////////////////////////////////////
+ // characterCount
+
+ testCharacterCount(IDs, 13);
+
+ ////////////////////////////////////////////////////////////////////////
+ // getText
+
+ testText(IDs, 0, 1, "h");
+ testText(IDs, 5, 7, " " + kEmbedChar);
+ testText(IDs, 10, 13, "e " + kEmbedChar);
+ testText(IDs, 0, 13, "hello " + kEmbedChar + " see " + kEmbedChar);
+
+ ////////////////////////////////////////////////////////////////////////
+ // getTextAtOffset line boundary
+
+ testTextAtOffset(0, BOUNDARY_LINE_START, "line ", 0, 5,
+ "hypertext3", kOk, kOk, kOk);
+
+ // XXX: see bug 634202.
+ testTextAtOffset(0, BOUNDARY_LINE_START, "line ", 0, 5,
+ "hypertext4", kTodo, kOk, kTodo);
+
+ //////////////////////////////////////////////////////////////////////////
+ // list
+ //////////////////////////////////////////////////////////////////////////
+
+ IDs = [ "list" ];
+ testCharacterCount(IDs, 2);
+ testText(IDs, 0, 2, kEmbedChar + kEmbedChar);
+
+ IDs = [ "listitem" ];
+ testCharacterCount(IDs, 6);
+ testText(IDs, 0, 6, "1. foo");
+
+ IDs = [ "listitemnone" ];
+ testCharacterCount(IDs, 3);
+ testText(IDs, 0, 3, "bar");
+
+ testText(["testbr"], 0, 3, "foo");
+
+ testTextAtOffset(2, nsIAccessibleText.BOUNDARY_CHAR, "o", 2, 3, "testbr",
+ kOk, kOk, kOk);
+ testTextAtOffset(2, nsIAccessibleText.BOUNDARY_WORD_START, "foo\n", 0, 4,
+ "testbr", kTodo, kOk, kTodo);
+ testTextBeforeOffset(2, nsIAccessibleText.BOUNDARY_LINE_START, "foo\n",
+ 0, 4, "testbr", kTodo, kOk, kTodo);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Fix getText"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=630001">
+ Bug 630001, part3
+ </a>
+ <a target="_blank"
+ title="getTextAtOffset line boundary may return more than one line"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=638326">
+ Bug 638326
+ </a>
+ <a target="_blank"
+ title="getText(0, -1) fails with empty text"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=749810">
+ Bug 749810
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="nulltext"></div>
+
+ <div id="hypertext">hello <a>friend</a> see <img src="about:blank"></div>
+ <div id="hypertext2">hello <a>friend</a> see <input></div>
+ <ol id="list">
+ <li id="listitem">foo</li>
+ <li id="listitemnone">bar</li>
+ </ol>
+
+ <div id="hypertext3">line
+<!-- haha -->
+<!-- hahaha -->
+<h6>heading</h6>
+ </div>
+
+ <div id="hypertext4">line
+<!-- haha -->
+<!-- hahaha -->
+<h6 role="presentation" class="gencontent">heading</h6>
+ </div>
+
+ <div id="testbr">foo<br/></div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/text/test_lineboundary.html b/accessible/tests/mochitest/text/test_lineboundary.html
new file mode 100644
index 000000000..8370d25d0
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_lineboundary.html
@@ -0,0 +1,265 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Line boundary getText* functions tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+ <script type="application/javascript">
+ function doTest()
+ {
+ testTextAtOffset("line_test_1", BOUNDARY_LINE_START,
+ [[0, 6, "Line 1 ", 0, 7],
+ [7, 7, "", 7, 7],
+ [8, 15, "Line 3 ", 8, 15]]);
+
+ //////////////////////////////////////////////////////////////////////////
+ // __h__e__l__l__o__ __m__y__ __f__r__i__e__n__d__
+ // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+
+ var IDs = [ "input", "div", "editable", "textarea",
+ getNode("ta", getNode("ta_cntr").contentDocument) ];
+
+ testTextBeforeOffset(IDs, BOUNDARY_LINE_START,
+ [ [ 0, 15, "", 0, 0 ] ]);
+ testTextBeforeOffset(IDs, BOUNDARY_LINE_END,
+ [ [ 0, 15, "", 0, 0 ] ]);
+
+ testTextAtOffset(IDs, BOUNDARY_LINE_START,
+ [ [ 0, 15, "hello my friend", 0, 15 ] ]);
+ testTextAtOffset(IDs, BOUNDARY_LINE_END,
+ [ [ 0, 15, "hello my friend", 0, 15 ] ]);
+
+ testTextAfterOffset(IDs, BOUNDARY_LINE_START,
+ [ [ 0, 15, "", 15, 15 ] ]);
+ testTextAfterOffset(IDs, BOUNDARY_LINE_END,
+ [ [ 0, 15, "", 15, 15 ] ]);
+
+ //////////////////////////////////////////////////////////////////////////
+ // __o__n__e__w__o__r__d__\n
+ // 0 1 2 3 4 5 6 7
+ // __\n
+ // 8
+ // __t__w__o__ __w__o__r__d__s__\n
+ // 9 10 11 12 13 14 15 16 17 18
+
+ IDs = [ "ml_div", "ml_divbr", "ml_editable", "ml_editablebr", "ml_textarea"];
+
+ testTextBeforeOffset(IDs, BOUNDARY_LINE_START,
+ [ [ 0, 7, "", 0, 0 ],
+ [ 8, 8, "oneword\n", 0, 8 ],
+ [ 9, 18, "\n", 8, 9 ],
+ [ 19, 19, "two words\n", 9, 19 ]]);
+
+ testTextBeforeOffset(IDs, BOUNDARY_LINE_END,
+ [ [ 0, 7, "", 0, 0 ],
+ [ 8, 8, "oneword", 0, 7 ],
+ [ 9, 18, "\n", 7, 8 ],
+ [ 19, 19, "\ntwo words", 8, 18 ]]);
+
+ testTextAtOffset(IDs, BOUNDARY_LINE_START,
+ [ [ 0, 7, "oneword\n", 0, 8 ],
+ [ 8, 8, "\n", 8, 9 ],
+ [ 9, 18, "two words\n", 9, 19 ],
+ [ 19, 19, "", 19, 19 ]]);
+ testTextAtOffset(IDs, BOUNDARY_LINE_END,
+ [ [ 0, 7, "oneword", 0, 7 ],
+ [ 8, 8, "\n", 7, 8 ],
+ [ 9, 18, "\ntwo words", 8, 18 ],
+ [ 19, 19, "\n", 18, 19 ]]);
+
+ testTextAfterOffset(IDs, BOUNDARY_LINE_START,
+ [ [ 0, 7, "\n", 8, 9 ],
+ [ 8, 8, "two words\n", 9, 19 ],
+ [ 9, 19, "", 19, 19 ]]);
+ testTextAfterOffset(IDs, BOUNDARY_LINE_END,
+ [ [ 0, 7, "\n", 7, 8 ],
+ [ 8, 8, "\ntwo words", 8, 18 ],
+ [ 9, 18, "\n", 18, 19 ],
+ [ 19, 19, "", 19, 19 ]]);
+
+ //////////////////////////////////////////////////////////////////////////
+ // a * b (* is embedded char for link)
+ testTextBeforeOffset([ getAccessible("ht_1").firstChild ], BOUNDARY_LINE_START,
+ [ [ 0, 5, "", 0, 0 ] ]);
+
+ testTextBeforeOffset([ getAccessible("ht_1").firstChild ], BOUNDARY_LINE_END,
+ [ [ 0, 5, "", 0, 0 ] ]);
+
+ testTextAtOffset([ getAccessible("ht_1").firstChild ], BOUNDARY_LINE_START,
+ [ [ 0, 5, "a " + kEmbedChar + " c", 0, 5 ] ]);
+
+ testTextAtOffset([ getAccessible("ht_1").firstChild ], BOUNDARY_LINE_END,
+ [ [ 0, 5, "a " + kEmbedChar + " c", 0, 5 ] ]);
+
+ testTextAfterOffset([ getAccessible("ht_1").firstChild ], BOUNDARY_LINE_START,
+ [ [ 0, 5, "", 5, 5 ] ]);
+
+ testTextAfterOffset([ getAccessible("ht_1").firstChild ], BOUNDARY_LINE_END,
+ [ [ 0, 5, "", 5, 5 ] ]);
+
+ //////////////////////////////////////////////////////////////////////////
+ // foo<br> and foo<br><br>
+
+ testTextAtOffset([ getAccessible("ht_2").firstChild.firstChild ],
+ BOUNDARY_LINE_START,
+ [ [ 0, 3, "foo", 0, 3 ] ]);
+ testTextAtOffset([ getAccessible("ht_3").firstChild.firstChild ],
+ BOUNDARY_LINE_START,
+ [ [ 0, 3, "foo\n", 0, 4 ], [ 4, 4, "", 4, 4 ] ]);
+
+ //////////////////////////////////////////////////////////////////////////
+ // 'Hello world ' (\n is rendered as space)
+
+ testTextAtOffset([ "ht_4" ], BOUNDARY_LINE_START,
+ [ [ 0, 12, "Hello world ", 0, 12 ] ]);
+
+ //////////////////////////////////////////////////////////////////////////
+ // list items
+
+ testTextAtOffset([ "li1" ], BOUNDARY_LINE_START,
+ [ [ 0, 6, kDiscBulletText + "Item", 0, 6 ] ]);
+ testTextAtOffset([ "li2" ], BOUNDARY_LINE_START,
+ [ [ 0, 2, kDiscBulletText, 0, 2 ] ]);
+ testTextAtOffset([ "li3" ], BOUNDARY_LINE_START,
+ [ [ 0, 8, kDiscBulletText + "a long ", 0, 9 ],
+ [ 9, 12, "and ", 9, 13 ] ]);
+ testTextAtOffset([ "li4" ], BOUNDARY_LINE_START,
+ [ [ 0, 7, kDiscBulletText + "a " + kEmbedChar + " c", 0, 7 ] ]);
+ testTextAtOffset([ "li5" ], BOUNDARY_LINE_START,
+ [ [ 0, 2, kDiscBulletText + "\n", 0, 3 ],
+ [ 3, 7, "hello", 3, 8 ] ]);
+ testTextAtOffset([ "ul1" ], BOUNDARY_LINE_START,
+ [ [ 0, 0, kEmbedChar, 0, 1 ],
+ [ 1, 1, kEmbedChar, 1, 2 ],
+ [ 2, 2, kEmbedChar, 2, 3 ],
+ [ 3, 3, kEmbedChar, 3, 4 ],
+ [ 4, 5, kEmbedChar, 4, 5 ] ]);
+
+ testTextAtOffset([ "li6" ], BOUNDARY_LINE_START,
+ [ [ 0, 7, "1. Item", 0, 7 ] ]);
+ testTextAtOffset([ "li7" ], BOUNDARY_LINE_START,
+ [ [ 0, 3, "2. ", 0, 3 ] ]);
+ testTextAtOffset([ "li8" ], BOUNDARY_LINE_START,
+ [ [ 0, 9, "3. a long ", 0, 10 ],
+ [ 10, 13, "and ", 10, 14 ] ]);
+ testTextAtOffset([ "li9" ], BOUNDARY_LINE_START,
+ [ [ 0, 8, "4. a " + kEmbedChar + " c", 0, 8 ] ]);
+ testTextAtOffset([ "li10" ], BOUNDARY_LINE_START,
+ [ [ 0, 3, "5. \n", 0, 4 ],
+ [ 4, 8, "hello", 4, 9 ] ]);
+ testTextAtOffset([ "ol1" ], BOUNDARY_LINE_START,
+ [ [ 0, 0, kEmbedChar, 0, 1 ],
+ [ 1, 1, kEmbedChar, 1, 2 ],
+ [ 2, 2, kEmbedChar, 2, 3 ],
+ [ 3, 3, kEmbedChar, 3, 4 ],
+ [ 4, 5, kEmbedChar, 4, 5 ] ]);
+
+ //////////////////////////////////////////////////////////////////////////
+ // Nested hypertexts
+
+ testTextAtOffset(["ht_5" ], BOUNDARY_LINE_START,
+ [ [ 0, 0, kEmbedChar, 0, 1 ] ]);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="getTextAtOffset for word boundaries: beginning of a new life"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=853340">
+ Bug 853340
+ </a>
+ <a target="_blank"
+ title="getTextBeforeOffset for word boundaries: evolving"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=855732">
+ Bug 855732
+ </a>
+ <a target="_blank"
+ title=" getTextAfterOffset for line boundary on new rails"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=882292">
+ Bug 882292
+ </a>
+ <a target="_blank"
+ title="getTextAtOffset broken for last object when closing tag is preceded by newline char"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=947170">
+ Bug 947170
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="input" value="hello my friend"/>
+ <div id="div">hello my friend</div>
+ <div id="editable" contenteditable="true">hello my friend</div>
+ <textarea id="textarea">hello my friend</textarea>
+ <iframe id="ta_cntr"
+ src="data:text/html,<html><body><textarea id='ta'>hello my friend</textarea></body></html>"></iframe>
+
+ <pre>
+ <div id="ml_div" style="border-style:outset;">oneword
+
+two words
+</div>
+ <div id="ml_divbr" style="border-style:outset;">oneword<br/><br/>two words<br/><br/></div>
+ <div id="ml_editable" style="border-style:outset;" contenteditable="true">oneword
+
+two words
+</div>
+ <div id="ml_editablebr" contenteditable="true" style="border-style:outset;">oneword<br/><br/>two words<br/><br/></div>
+ <textarea id="ml_textarea" cols="300">oneword
+
+two words
+</textarea>
+ </pre>
+
+ <iframe id="ht_1" src="data:text/html,<html><body>a <a href=''>b</a> c</body></html>"></iframe>
+
+ <iframe id="ht_2" src="data:text/html,<div contentEditable='true'>foo<br/></div>"></iframe>
+ <iframe id="ht_3" src="data:text/html,<div contentEditable='true'>foo<br/><br/></div>"></iframe>
+
+ <p id="ht_4">Hello world
+</p>
+
+ <ul id="ul1">
+ <li id="li1">Item</li>
+ <li id="li2"></li>
+ <li id="li3" style="font-family:monospace; font-size:10pt; width:8ch;">a long and winding road that lead me to your door</li>
+ <li id="li4">a <a href=''>b</a> c</li>
+ <li id="li5"><br>hello</li>
+ </ul>
+
+ <ol id="ol1">
+ <li id="li6">Item</li>
+ <li id="li7"></li>
+ <li id="li8" style="font-family:monospace; font-size:10pt; width:8ch;">a long and winding road that lead me to your door</li>
+ <li id="li9">a <a href=''>b</a> c</li>
+ <li id="li10"><br>hello</li>
+ </ol>
+
+ <div id="ht_5">
+ <div>
+ <p>sectiounus</p>
+ <p>seciofarus</p>
+ </div>
+ </div>
+ <div id="line_test_1">
+ Line 1
+ <center><input type="TEXT"><input value="Button" type="SUBMIT"></center>
+ Line 3
+ </div>
+ </body>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/text/test_passwords.html b/accessible/tests/mochitest/text/test_passwords.html
new file mode 100644
index 000000000..8a47b5944
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_passwords.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>nsIAccessibleText getText related function tests for text and password inputs</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // regular text and password inputs
+ //////////////////////////////////////////////////////////////////////////
+
+ ////////////////////////////////////////////////////////////////////////
+ // characterCount and getText for regular text field
+
+ var IDs = [ "username" ];
+ testCharacterCount(IDs, 4);
+ testText(IDs, 0, 4, "test");
+
+ ////////////////////////////////////////////////////////////////////////
+ // characterCount and getText for password field
+
+ IDs = [ "password" ];
+ testCharacterCount(IDs, 4);
+ testPasswordText(IDs, 0, 4, "test");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="mochitest for getText for password fields"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=415943">Mozilla Bug 415943</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <form action="post.php" method="post">
+ <label for="username">User name:</label>
+ <input id="username" value="test"><br />
+ <label for="password">Password:</label>
+ <input type="password" id="password" value="test"/>
+ </form>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/text/test_selection.html b/accessible/tests/mochitest/text/test_selection.html
new file mode 100644
index 000000000..b832231cf
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_selection.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test text selection functions</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/MochiKit/packed.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest()
+ {
+ // Test selection count: clean selection / check count.
+ testTextAddSelection("div0", 0, 2, 1); // |Test selection...
+ cleanTextSelections("div0");
+ testTextSelectionCount("div0", 0);
+
+ // Test addition: adding two equal selections, the second one should
+ // not be added.
+ testTextAddSelection("div1", 7, 9, 1); // Test ad|di|ng two...
+ testTextAddSelection("div1", 7, 9, 1); // Test ad|di|ng two...
+ testTextGetSelection("div1", 7, 9, 0);
+
+ // Test overlapping selections: adding three selections, one adjacent.
+ testTextAddSelection("div2", 0, 3, 1); // |Tes|t adding 3...
+ testTextAddSelection("div2", 7, 9, 2); // |Tes|t ad|di|ng 3...
+ testTextAddSelection("div2", 3, 4, 3); // |Tes||t| ad|di|ng 3...
+ testTextGetSelection("div2", 0, 3, 0);
+ testTextGetSelection("div2", 3, 4, 1);
+ testTextGetSelection("div2", 7, 9, 2);
+
+ // Test selection re-ordering: adding two selections.
+ // NOTE: removeSelections aSelectionIndex is from start of document.
+ testTextAddSelection("div3", 0, 3, 1); // |Tes|t adding 2...
+ testTextAddSelection("div3", 7, 9, 2); // |Tes|t ad|di|ng 2...
+ testTextRemoveSelection("div3", 4, 1); // Test ad|di|ng 2...
+
+ // Test extending existing selection.
+ // NOTE: setSelectionBounds aSelectionIndex is from start of document.
+ testTextAddSelection("div4", 4, 5, 1); // Test| |extending...
+ testTextSetSelection("div4", 4, 9, 6, 1); // Test| exte|nding...
+
+ // Test moving an existing selection.
+ // NOTE: setSelectionBounds aSelectionIndex is from start of document.
+ testTextAddSelection("div5", 1, 3, 1); // T|es|t moving...
+ testTextSetSelection("div5", 5, 9, 6, 1); // Test |movi|ng...
+
+ // Test adding selections to multiple inner elements.
+ testTextAddSelection("div71", 0, 3, 1); // |Tes|t adding...
+ testTextAddSelection("div71", 7, 8, 2); // |Tes|t ad|d|ing...
+ testTextAddSelection("div72", 4, 6, 1); // Test| a|dding...
+ testTextAddSelection("div72", 7, 8, 2); // Test| a|d|d|ing...
+
+ // Test adding selection to parent element.
+ // NOTE: If inner elements are represented as embedded chars
+ // we count their internal selections.
+ testTextAddSelection("div7", 7, 8, 5); // Test ad|d|ing...
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+
+</script>
+</head>
+
+<body>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="div0">Test selection count</div>
+ </br>
+ <div id="div1">Test adding two equal selections </div>
+ <div id="div2">Test adding 3 selections one adjacent </div>
+ <div id="div3">Test adding 2 selections, remove first one </div>
+ <div id="div4">Test extending a selection </div>
+ <div id="div5">Test moving a selection </div>
+ </br>
+ <div id="div7">Test adding selections to parent element
+ <div id="div71">Test adding selections to inner element1 </div>
+ <div id="div72">Test adding selections to inner element2 </div>
+ </div>
+
+</body>
+
+</html>
diff --git a/accessible/tests/mochitest/text/test_wordboundary.html b/accessible/tests/mochitest/text/test_wordboundary.html
new file mode 100644
index 000000000..6d3c09e8c
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_wordboundary.html
@@ -0,0 +1,291 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Word boundary text tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ // "hello"
+ // __h__e__l__l__o__
+ // 0 1 2 3 4 5
+ var ids = [ "i1", "d1", "e1", "t1" ];
+ testTextBeforeOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "", 0, 0 ] ]);
+ testTextBeforeOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 5, "", 0, 0 ] ]);
+
+ testTextAtOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "hello", 0, 5 ] ]);
+ testTextAtOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 5, "hello", 0, 5 ] ]);
+
+ testTextAfterOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "", 5, 5 ] ]);
+ testTextAfterOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 5, "", 5, 5 ] ]);
+
+ // "hello "
+ // __h__e__l__l__o__ __
+ // 0 1 2 3 4 5 6
+ var ids = [ "i2", "d2", "e2", "t2" ];
+ testTextBeforeOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 6, "", 0, 0 ] ]);
+ testTextBeforeOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 5, "", 0, 0 ],
+ [ 6, 6, "hello", 0, 5 ]
+ ]);
+
+ testTextAtOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 6, "hello ", 0, 6 ] ]);
+ testTextAtOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 4, "hello", 0, 5 ],
+ [ 5, 6, " ", 5, 6 ]
+ ]);
+
+ testTextAfterOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 6, "", 6, 6 ] ]);
+ testTextAfterOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 5, " ", 5, 6 ],
+ [ 6, 6, "", 6, 6 ]
+ ]);
+
+ // "hello all"
+ // __h__e__l__l__o__ __a__l__l__
+ // 0 1 2 3 4 5 6 7 8 9
+ ids = [ "i6", "d6", "e6", "t6" ];
+ testTextBeforeOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "", 0, 0 ],
+ [ 6, 9, "hello ", 0, 6 ]]);
+ testTextBeforeOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 5, "", 0, 0 ],
+ [ 6, 9, "hello", 0, 5 ] ]);
+
+ testTextAtOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "hello ", 0, 6 ],
+ [ 6, 9, "all", 6, 9 ] ]);
+ testTextAtOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 4, "hello", 0, 5 ],
+ [ 5, 9, " all", 5, 9 ] ]);
+
+ testTextAfterOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "all", 6, 9 ],
+ [ 6, 9, "", 9, 9 ] ]);
+ testTextAfterOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 5, " all", 5, 9 ],
+ [ 6, 9, "", 9, 9 ] ]);
+
+ // "hello my friend"
+ // __h__e__l__l__o__ __m__y__ __f__r__i__e__n__d__
+ // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ ids = [ "i7", "d7", "e7", "t7" ];
+ testTextBeforeOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "", 0, 0 ],
+ [ 6, 8, "hello ", 0, 6 ],
+ [ 9, 15, "my ", 6, 9 ] ]);
+ testTextBeforeOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 5, "", 0, 0 ],
+ [ 6, 8, "hello", 0, 5 ],
+ [ 9, 15, " my", 5, 8 ] ]);
+
+ testTextAtOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "hello ", 0, 6 ],
+ [ 6, 8, "my ", 6, 9 ],
+ [ 9, 15, "friend", 9, 15] ]);
+ testTextAtOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 4, "hello", 0, 5 ],
+ [ 5, 7, " my", 5, 8 ],
+ [ 8, 15, " friend", 8, 15] ]);
+
+ testTextAfterOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "my ", 6, 9 ],
+ [ 6, 8, "friend", 9, 15 ],
+ [ 9, 15, "", 15, 15 ] ]);
+ testTextAfterOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 5, " my", 5, 8 ],
+ [ 6, 8, " friend", 8, 15 ],
+ [ 9, 15, "", 15, 15 ] ]);
+
+ // "Brave Sir Robin ran"
+ // __B__r__a__v__e__ __S__i__r__ __ __R__o__b__i__n__ __ __ __r__a__n__
+ // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
+ ids = [ "i8", "d8", "e8", "t8" ];
+ testTextBeforeOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "", 0, 0 ],
+ [ 6, 10, "Brave ", 0, 6 ],
+ [ 11, 18, "Sir ", 6, 11 ],
+ [ 19, 22, "Robin ", 11, 19 ] ]);
+ testTextBeforeOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 5, "", 0, 0 ],
+ [ 6, 9, "Brave", 0, 5 ],
+ [ 10, 16, " Sir", 5, 9 ],
+ [ 17, 22, " Robin", 9, 16 ] ]);
+
+ testTextAtOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "Brave ", 0, 6 ],
+ [ 6, 10, "Sir ", 6, 11 ],
+ [ 11, 18, "Robin ", 11, 19 ],
+ [ 19, 22, "ran", 19, 22 ] ]);
+ testTextAtOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 4, "Brave", 0, 5 ],
+ [ 5, 8, " Sir", 5, 9 ],
+ [ 9, 15, " Robin", 9, 16 ],
+ [ 16, 22, " ran", 16, 22 ] ]);
+
+ testTextAfterOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 5, "Sir ", 6, 11 ],
+ [ 6, 10, "Robin ", 11, 19 ],
+ [ 11, 18, "ran", 19, 22 ],
+ [ 19, 22, "", 22, 22 ] ]);
+ testTextAfterOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 5, " Sir", 5, 9 ],
+ [ 6, 9, " Robin", 9, 16 ],
+ [ 10, 16, " ran", 16, 22 ],
+ [ 17, 22, "", 22, 22 ] ]);
+
+ // 'oneword
+ // '
+ // 'two words
+ // '
+ // __o__n__e__w__o__r__d__\n
+ // 0 1 2 3 4 5 6 7
+ // __\n
+ // 8
+ // __t__w__o__ __w__o__r__d__s__\n__
+ // 9 10 11 12 13 14 15 16 17 18 19
+
+ ids = ["ml_div1", "ml_divbr1", "ml_ediv1", "ml_edivbr1", "ml_t1"];
+ testTextBeforeOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 8, "", 0, 0 ],
+ [ 9, 12, "oneword\n\n", 0, 9 ],
+ [ 13, 19, "two ", 9, 13 ] ]);
+ testTextBeforeOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 7, "", 0, 0 ],
+ [ 8, 12, "oneword", 0, 7,
+ [ [ 8, "ml_divbr1", kTodo, kOk, kTodo ],
+ [ 8, "ml_edivbr1", kTodo, kOk, kTodo ],
+ [ 9, "ml_divbr1", kTodo, kOk, kTodo ],
+ [ 9, "ml_edivbr1", kTodo, kOk, kTodo ] ] ],
+ [ 13, 18, "\n\ntwo", 7, 12 ],
+ [ 19, 19, " words", 12, 18,
+ [ [ 19, "ml_divbr1", kTodo, kTodo, kTodo, ],
+ [ 19, "ml_edivbr1", kTodo, kTodo, kTodo, ] ] ]
+ ] );
+
+ testTextAtOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 8, "oneword\n\n", 0, 9,
+ [ [ 7, "ml_divbr1", kTodo, kTodo, kTodo ],
+ [ 7, "ml_edivbr1", kTodo, kTodo, kTodo ],
+ [ 8, "ml_divbr1", kTodo, kTodo, kTodo ],
+ [ 8, "ml_edivbr1", kTodo, kTodo, kTodo ] ] ],
+ [ 9, 12, "two ", 9, 13 ],
+ [ 13, 19, "words\n", 13, 19 ] ]);
+ testTextAtOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 6, "oneword", 0, 7 ],
+ [ 7, 11, "\n\ntwo", 7, 12 ],
+ [ 12, 17, " words", 12, 18 ],
+ [ 18, 19, "\n", 18, 19,
+ [ [ 18, "ml_divbr1", kTodo, kTodo, kOk ],
+ [ 18, "ml_edivbr1", kTodo, kTodo, kOk ],
+ [ 19, "ml_divbr1", kTodo, kTodo, kOk ],
+ [ 19, "ml_edivbr1", kTodo, kTodo, kOk ] ] ] ]);
+
+ testTextAfterOffset(ids, BOUNDARY_WORD_START,
+ [ [ 0, 8, "two ", 9, 13,
+ [ [ 7, "ml_divbr1", kTodo, kTodo, kTodo ],
+ [ 7, "ml_edivbr1", kTodo, kTodo, kTodo ],
+ [ 8, "ml_divbr1", kTodo, kTodo, kTodo ],
+ [ 8, "ml_edivbr1", kTodo, kTodo, kTodo ] ] ],
+ [ 9, 12, "words\n", 13, 19 ],
+ [ 13, 19, "", 19, 19 ] ]);
+ testTextAfterOffset(ids, BOUNDARY_WORD_END,
+ [ [ 0, 7, "\n\ntwo", 7, 12 ],
+ [ 8, 12, " words", 12, 18 ],
+ [ 13, 18, "\n", 18, 19,
+ [ [ 18, "ml_divbr1", kTodo, kTodo, kOk ],
+ [ 18, "ml_edivbr1", kTodo, kTodo, kOk ] ] ],
+ [ 19, 19, "", 19, 19 ] ]);
+
+ // a <a href="#">b</a>
+ // a *
+ testTextBeforeOffset("cntr_1", BOUNDARY_WORD_START,
+ [ [ 0, 1, "", 0, 0 ],
+ [ 2, 3, "a ", 0, 2 ] ]);
+
+ testTextAtOffset("cntr_1", BOUNDARY_WORD_START,
+ [ [ 0, 1, "a ", 0, 2 ],
+ [ 2, 3, kEmbedChar, 2, 3 ] ]);
+ testTextAfterOffset("cntr_1", BOUNDARY_WORD_START,
+ [ [ 0, 1, kEmbedChar, 2, 3 ],
+ [ 2, 3, "", 3, 3 ] ]);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="i1" value="hello"/>
+ <div id="d1">hello</div>
+ <div id="e1" contenteditable="true">hello</div>
+ <textarea id="t1">hello</textarea>
+
+ <input id="i2" value="hello "/>
+ <pre><div id="d2">hello </div></pre>
+ <div id="e2" contenteditable="true" style='white-space:pre'>hello </div>
+ <textarea id="t2">hello </textarea>
+
+ <input id="i6" value="hello all"/>
+ <div id="d6">hello all</div>
+ <div id="e6" contenteditable="true">hello all</div>
+ <textarea id="t6">hello all</textarea>
+
+ <input id="i7" value="hello my friend"/>
+ <div id="d7">hello my friend</div>
+ <div id="e7" contenteditable="true">hello my friend</div>
+ <textarea id="t7">hello my friend</textarea>
+
+ <input id="i8" value="Brave Sir Robin ran"/>
+ <pre>
+ <div id="d8">Brave Sir Robin ran</div>
+ <div id="e8" contenteditable="true">Brave Sir Robin ran</div>
+ </pre>
+ <textarea id="t8" cols="300">Brave Sir Robin ran</textarea>
+
+ <pre>
+<div id="ml_div1">oneword
+
+two words
+</div>
+<div id="ml_divbr1">oneword<br/><br/>two words<br/><br/></div>
+<div id="ml_ediv1" contenteditable="true">oneword
+
+two words
+</div>
+<div id="ml_edivbr1" contenteditable="true">oneword<br/><br/>two words<br/><br/></div>
+<textarea id="ml_t1" cols="300">oneword
+
+two words
+</textarea>
+ </pre>
+
+ <div id="cntr_1">a <a href="#">b</a></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/text/test_words.html b/accessible/tests/mochitest/text/test_words.html
new file mode 100644
index 000000000..dff90bfea
--- /dev/null
+++ b/accessible/tests/mochitest/text/test_words.html
@@ -0,0 +1,133 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>nsIAccessibleText getText related function tests for html:input,html:div and html:textarea</title>
+ <meta charset="utf-8" />
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+ <script type="application/javascript">
+ if (navigator.platform.startsWith("Mac")) {
+ SimpleTest.expectAssertions(0, 1);
+ } else {
+ SimpleTest.expectAssertions(0, 1);
+ }
+
+ function doTest()
+ {
+ // "one two"
+ testWords("div1", ["one", "two"]);
+
+ // "one two"
+ testWords("div2", ["one", "two"]);
+
+ // "one,two"
+ testWordCount("div3", 2, kOk);
+ testWordAt("div3", 0, "one", kTodo);
+ testWordAt("div3", 1, "two", kOk);
+
+ // "one, two"
+ testWordCount("div4", 2, kOk);
+ testWordAt("div4", 0, "one", kTodo);
+ testWordAt("div4", 1, "two", kOk);
+
+ // "one+two"
+ testWordCount("div5", 2, kOk);
+ testWordAt("div5", 0, "one", kTodo);
+ testWordAt("div5", 1, "two", kOk);
+
+ // "one+two "
+ testWordCount("div6", 2, kOk);
+ testWordAt("div6", 0, "one", kTodo);
+ testWordAt("div6", 1, "two", kOk);
+
+ // "one\ntwo"
+ testWordCount("div7", 2, kOk);
+ testWordAt("div7", 0, "one", kOk);
+ testWordAt("div7", 1, "two", kTodo);
+
+ // "one.two"
+ testWordCount("div8", 2, kOk);
+ testWordAt("div8", 0, "one", kTodo);
+ testWordAt("div8", 1, "two", kOk);
+
+ // "345"
+ testWords("div9", ["345"]);
+
+ // "3a A4"
+ testWords("div10", ["3a", "A4"]);
+
+ // "3.1416"
+ testWords("div11", ["3.1416"], kTodo);
+
+ // "4,261.01"
+ testWords("div12", ["4,261.01"], kTodo);
+
+ // "カタカナ"
+ testWords("div13", ["カタカナ"], kOk);
+
+ // "Peter's car"
+ testWords("div14", ["Peter's", "car"], kTodo);
+
+ // "N.A.T.O."
+ testWords("div15", ["N.A.T.O."], kTodo);
+
+ // "3+4*5=23"
+ testWordCount("div16", 4, kOk);
+ testWordAt("div15", 0, "3", kTodo);
+ testWordAt("div15", 1, "4", kTodo);
+ testWordAt("div15", 2, "5", kTodo);
+ testWordAt("div15", 3, "23", kTodo);
+
+ // "Hello. Friend, are you here?!"
+ testWordCount("div17", 5, kOk);
+ testWordAt("div17", 0, "Hello", kTodo);
+ testWordAt("div17", 1, "Friend", kTodo);
+ testWordAt("div17", 2, "are", kOk);
+ testWordAt("div17", 3, "you", kOk);
+ testWordAt("div17", 4, "here", kTodo);
+
+ testWords("input_1", ["foo", "bar"]);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="nsIAccessibleText test word boundaries"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=452769">Mozilla Bug 452769</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ <div id="div1">one two</div>
+ <div id="div2">one two</div>
+ <div id="div3">one,two</div>
+ <div id="div4">one, two</div>
+ <div id="div5">one+two</div>
+ <div id="div6">one+two </div>
+ <div id="div7">one<br/>two</div>
+ <div id="div8">one.two</div>
+ <div id="div9">345</div>
+ <div id="div10">3a A4</div>
+ <div id="div11">3.1416</div>
+ <div id="div12">4,261.01</div>
+ <div id="div13">カタカナ</div>
+ <div id="div14">Peter's car</div>
+ <div id="div15">N.A.T.O.</div>
+ <div id="div16">3+4*5=23</div>
+ <div id="div17">Hello. Friend, are you here?!</div>
+ </pre>
+ <input id="input_1" type="text" value="foo bar" placeholder="something or other">
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/textattrs/a11y.ini b/accessible/tests/mochitest/textattrs/a11y.ini
new file mode 100644
index 000000000..046bd57e8
--- /dev/null
+++ b/accessible/tests/mochitest/textattrs/a11y.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/moz.png
+
+[test_general.html]
+[test_invalid.html]
diff --git a/accessible/tests/mochitest/textattrs/test_general.html b/accessible/tests/mochitest/textattrs/test_general.html
new file mode 100644
index 000000000..142701b17
--- /dev/null
+++ b/accessible/tests/mochitest/textattrs/test_general.html
@@ -0,0 +1,735 @@
+<html>
+
+<head>
+ <title>Text attributes tests</title>
+ <meta charset="utf-8" />
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <style type="text/css">
+ .gencontent:before { content: "*"; }
+ .gencontent:after { content: "*"; }
+ </style>
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ var gComputedStyle = null;
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // area1
+ var ID = "area1";
+ var defAttrs = buildDefaultTextAttrs(ID, "10pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ var attrs = {};
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 7);
+
+ attrs = { "font-weight": kBoldFontWeight };
+ testTextAttrs(ID, 7, attrs, defAttrs, 7, 11);
+
+ attrs = {};
+ testTextAttrs(ID, 12, attrs, defAttrs, 11, 18);
+
+ //////////////////////////////////////////////////////////////////////////
+ // area2
+ ID = "area2";
+ defAttrs = buildDefaultTextAttrs(ID, "14pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ attrs = {};
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 7);
+
+ attrs = { "font-weight": kBoldFontWeight };
+ testTextAttrs(ID, 7, attrs, defAttrs, 7, 12);
+
+ var tempElem = getNode(ID).firstChild.nextSibling.firstChild.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
+ attrs = {"font-style": gComputedStyle.fontStyle,
+ "font-weight": kBoldFontWeight };
+ testTextAttrs(ID, 13, attrs, defAttrs, 12, 19);
+
+ attrs = { "font-weight": kBoldFontWeight };
+ testTextAttrs(ID, 20, attrs, defAttrs, 19, 23);
+
+ attrs = {};
+ testTextAttrs(ID, 24, attrs, defAttrs, 23, 30);
+
+ //////////////////////////////////////////////////////////////////////////
+ // area3
+ ID = "area3";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ tempElem = getNode(ID).firstChild.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
+ attrs = {"color": gComputedStyle.color};
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 6);
+
+ tempElem = tempElem.firstChild.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
+ attrs = {"color": gComputedStyle.color};
+ testTextAttrs(ID, 6, attrs, defAttrs, 6, 26);
+
+ tempElem = tempElem.parentNode;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
+ attrs = {"color": gComputedStyle.color};
+ testTextAttrs(ID, 26, attrs, defAttrs, 26, 27);
+
+ tempElem = tempElem.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
+ attrs = {"color": gComputedStyle.color,
+ "background-color": gComputedStyle.backgroundColor};
+ testTextAttrs(ID, 27, attrs, defAttrs, 27, 50);
+
+ //////////////////////////////////////////////////////////////////////////
+ // area4
+ ID = "area4";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ tempElem = getNode(ID).firstChild.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
+ attrs = {"color": gComputedStyle.color};
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 16);
+
+ tempElem = tempElem.nextSibling.firstChild.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
+ attrs = {"color": gComputedStyle.color};
+ testTextAttrs(ID, 16, attrs, defAttrs, 16, 33);
+
+ tempElem = tempElem.parentNode;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
+ attrs = {"color": gComputedStyle.color};
+ testTextAttrs(ID, 34, attrs, defAttrs, 33, 46);
+
+ //////////////////////////////////////////////////////////////////////////
+ // area5: "Green!*!RedNormal"
+ ID = "area5";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ // Green
+ tempElem = getNode(ID).firstChild.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
+ attrs = {"color": gComputedStyle.color};
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 5);
+
+ // br
+ attrs = {};
+ testTextAttrs(ID, 5, attrs, defAttrs, 5, 6);
+
+ // img, embedded accessible, no attributes
+ attrs = {};
+ testTextAttrs(ID, 6, attrs, {}, 6, 7);
+
+ // br
+ attrs = {};
+ testTextAttrs(ID, 7, attrs, defAttrs, 7, 8);
+
+ // Red
+ tempElem = tempElem.nextSibling.nextSibling.nextSibling.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
+ attrs = {"color": gComputedStyle.color};
+ testTextAttrs(ID, 9, attrs, defAttrs, 8, 11);
+
+ // Normal
+ attrs = {};
+ testTextAttrs(ID, 11, attrs, defAttrs, 11, 18);
+
+ //////////////////////////////////////////////////////////////////////////
+ // area6 (CSS vertical-align property, refer to bug 445938 for details
+ // and sup and sub elements, refer to bug 735645 for details)
+ ID = "area6";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ attrs = {};
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 5);
+
+ attrs = { "text-position": "super", "font-size": "10pt" };
+ testTextAttrs(ID, 5, attrs, defAttrs, 5, 13);
+
+ attrs = {};
+ testTextAttrs(ID, 13, attrs, defAttrs, 13, 27);
+
+ attrs = { "text-position": "super" };
+ testTextAttrs(ID, 27, attrs, defAttrs, 27, 35);
+
+ attrs = {};
+ testTextAttrs(ID, 35, attrs, defAttrs, 35, 39);
+
+ attrs = { "text-position": "sub", "font-size": "10pt" };
+ testTextAttrs(ID, 39, attrs, defAttrs, 39, 50);
+
+ attrs = {};
+ testTextAttrs(ID, 50, attrs, defAttrs, 50, 55);
+
+ attrs = { "text-position": "sub" };
+ testTextAttrs(ID, 55, attrs, defAttrs, 55, 64);
+
+ attrs = {};
+ testTextAttrs(ID, 64, attrs, defAttrs, 64, 69);
+
+ attrs = { "text-position": "super" };
+ testTextAttrs(ID, 69, attrs, defAttrs, 69, 84);
+
+ attrs = {};
+ testTextAttrs(ID, 84, attrs, defAttrs, 84, 89);
+
+ attrs = { "text-position": "sub" };
+ testTextAttrs(ID, 89, attrs, defAttrs, 89, 102);
+
+ attrs = {};
+ testTextAttrs(ID, 102, attrs, defAttrs, 102, 107);
+
+ attrs = { "text-position": "super" };
+ testTextAttrs(ID, 107, attrs, defAttrs, 107, 123);
+
+ attrs = {};
+ testTextAttrs(ID, 123, attrs, defAttrs, 123, 128);
+
+ attrs = { "text-position": "sub" };
+ testTextAttrs(ID, 128, attrs, defAttrs, 128, 142);
+
+ //////////////////////////////////////////////////////////////////////////
+ // area7
+ ID = "area7";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+ defAttrs["language"] = "en";
+ testDefaultTextAttrs(ID, defAttrs);
+
+ attrs = {"language": "ru"};
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 6);
+
+ attrs = {};
+ testTextAttrs(ID, 6, attrs, defAttrs, 6, 7);
+
+ tempElem = getNode(ID).firstChild.nextSibling.nextSibling.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
+ attrs = { "background-color": gComputedStyle.backgroundColor};
+ testTextAttrs(ID, 13, attrs, defAttrs, 7, 20);
+
+ attrs = {};
+ testTextAttrs(ID, 20, attrs, defAttrs, 20, 21);
+
+ attrs = {"language": "de"};
+ testTextAttrs(ID, 21, attrs, defAttrs, 21, 36);
+
+ attrs = {};
+ testTextAttrs(ID, 36, attrs, defAttrs, 36, 44);
+
+ attrs = {};
+ testTextAttrs(ID, 37, attrs, defAttrs, 36, 44);
+
+ tempElem = tempElem.nextSibling.nextSibling.nextSibling.nextSibling.firstChild.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
+ attrs = {"color": gComputedStyle.color};
+ testTextAttrs(ID, 44, attrs, defAttrs, 44, 51);
+
+ tempElem = tempElem.firstChild.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
+ attrs = {"font-weight": kBoldFontWeight,
+ "color": gComputedStyle.color};
+ testTextAttrs(ID, 51, attrs, defAttrs, 51, 55);
+
+ tempElem = tempElem.parentNode;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
+ attrs = {"color": gComputedStyle.color};
+ testTextAttrs(ID, 55, attrs, defAttrs, 55, 62);
+
+ //////////////////////////////////////////////////////////////////////////
+ // area9, different single style spans in styled paragraph
+ ID = "area9";
+ defAttrs = buildDefaultTextAttrs(ID, "10pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ attrs = {};
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 6);
+
+ attrs = { "font-size": "12pt" };
+ testTextAttrs(ID, 7, attrs, defAttrs, 6, 12);
+
+ attrs = {};
+ testTextAttrs(ID, 13, attrs, defAttrs, 12, 21);
+
+ // Walk to the span with the different background color
+ tempElem = getNode(ID).firstChild.nextSibling.nextSibling.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
+ attrs = { "background-color": gComputedStyle.backgroundColor };
+ testTextAttrs(ID, 22, attrs, defAttrs, 21, 36);
+
+ attrs = {};
+ testTextAttrs(ID, 37, attrs, defAttrs, 36, 44);
+
+ // Walk from the background color span to the one with font-style
+ tempElem = tempElem.nextSibling.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
+ attrs = { "font-style": gComputedStyle.fontStyle };
+ testTextAttrs(ID, 45, attrs, defAttrs, 44, 61);
+
+ attrs = {};
+ testTextAttrs(ID, 62, attrs, defAttrs, 61, 69);
+
+ // Walk from span with font-style to the one with font-family.
+ tempElem = tempElem.nextSibling.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
+ attrs = { "font-family": kMonospaceFontFamily };
+ testTextAttrs(ID, 70, attrs, defAttrs, 69, 83);
+
+ attrs = {};
+ testTextAttrs(ID, 84, attrs, defAttrs, 83, 91);
+
+ attrs = {
+ "text-underline-style": "solid",
+ "text-underline-color": gComputedStyle.color
+ };
+ testTextAttrs(ID, 92, attrs, defAttrs, 91, 101);
+
+ attrs = {};
+ testTextAttrs(ID, 102, attrs, defAttrs, 101, 109);
+
+ attrs = {
+ "text-line-through-style": "solid",
+ "text-line-through-color": gComputedStyle.color
+ };
+ testTextAttrs(ID, 110, attrs, defAttrs, 109, 122);
+
+ attrs = {};
+ testTextAttrs(ID, 123, attrs, defAttrs, 122, 130);
+
+ attrs = {
+ "text-line-through-style": "solid",
+ "text-line-through-color": gComputedStyle.color
+ };
+ testTextAttrs(ID, 131, attrs, defAttrs, 130, 143);
+
+ attrs = {};
+ testTextAttrs(ID, 144, attrs, defAttrs, 143, 151);
+
+ attrs = {
+ "text-line-through-style": "solid",
+ "text-line-through-color": gComputedStyle.color
+ };
+ testTextAttrs(ID, 152, attrs, defAttrs, 151, 164);
+
+ attrs = {};
+ testTextAttrs(ID, 165, attrs, defAttrs, 164, 172);
+
+ //////////////////////////////////////////////////////////////////////////
+ // area10, different single style spans in non-styled paragraph
+ ID = "area10";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ attrs = {};
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 7);
+
+ attrs = { "font-size": "14pt" };
+ testTextAttrs(ID, 7, attrs, defAttrs, 7, 13);
+
+ attrs = {};
+ testTextAttrs(ID, 13, attrs, defAttrs, 13, 22);
+
+ // Walk to the span with the different background color
+ tempElem = getNode(ID).firstChild.nextSibling.nextSibling.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
+ attrs = { "background-color": gComputedStyle.backgroundColor };
+ testTextAttrs(ID, 23, attrs, defAttrs, 22, 37);
+
+ attrs = {};
+ testTextAttrs(ID, 38, attrs, defAttrs, 37, 45);
+
+ // Walk from the background color span to the one with font-style
+ tempElem = tempElem.nextSibling.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
+ attrs = {"font-style": gComputedStyle.fontStyle};
+ testTextAttrs(ID, 46, attrs, defAttrs, 45, 62);
+
+ attrs = {};
+ testTextAttrs(ID, 63, attrs, defAttrs, 62, 70);
+
+ // Walk from span with font-style to the one with font-family.
+ tempElem = tempElem.nextSibling.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
+ attrs = { "font-family": kMonospaceFontFamily };
+ testTextAttrs(ID, 71, attrs, defAttrs, 70, 84);
+
+ attrs = {};
+ testTextAttrs(ID, 85, attrs, defAttrs, 84, 92);
+
+ attrs = {
+ "text-underline-style": "solid",
+ "text-underline-color": gComputedStyle.color
+ };
+ testTextAttrs(ID, 93, attrs, defAttrs, 92, 102);
+
+ attrs = {};
+ testTextAttrs(ID, 103, attrs, defAttrs, 102, 110);
+
+ attrs = {
+ "text-line-through-style": "solid",
+ "text-line-through-color": gComputedStyle.color
+ };
+ testTextAttrs(ID, 111, attrs, defAttrs, 110, 123);
+
+ attrs = {};
+ testTextAttrs(ID, 124, attrs, defAttrs, 123, 131);
+
+ //////////////////////////////////////////////////////////////////////////
+ // area11, "font-weight" tests
+ ID = "area11";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt", kBoldFontWeight);
+ testDefaultTextAttrs(ID, defAttrs);
+
+ attrs = { };
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 13);
+
+ attrs = { "font-weight": kNormalFontWeight };
+ testTextAttrs(ID, 13, attrs, defAttrs, 13, 20);
+
+ attrs = { };
+ testTextAttrs(ID, 20, attrs, defAttrs, 20, 27);
+
+ attrs = { "font-weight": kNormalFontWeight };
+ testTextAttrs(ID, 27, attrs, defAttrs, 27, 33);
+
+ attrs = { };
+ testTextAttrs(ID, 33, attrs, defAttrs, 33, 51);
+
+ attrs = { "font-weight": kNormalFontWeight };
+ testTextAttrs(ID, 51, attrs, defAttrs, 51, 57);
+
+ attrs = { };
+ testTextAttrs(ID, 57, attrs, defAttrs, 57, 97);
+
+ //////////////////////////////////////////////////////////////////////////
+ // test out of range offset
+ testTextAttrsWrongOffset("area12", -1);
+ testTextAttrsWrongOffset("area12", 500);
+
+ //////////////////////////////////////////////////////////////////////////
+ // test zero offset on empty hypertext accessibles
+ ID = "area13";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+ attrs = { };
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 0);
+
+ ID = "area14";
+ defAttrs = buildDefaultTextAttrs(ID, kInputFontSize,
+ kNormalFontWeight, kInputFontFamily);
+
+ attrs = { };
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 0);
+
+ //////////////////////////////////////////////////////////////////////////
+ // area15, embed char tests, "*plain*plain**bold*bold*"
+ ID = "area15";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+
+ // p
+ testTextAttrs(ID, 0, { }, { }, 0, 1);
+ // plain
+ testTextAttrs(ID, 1, { }, defAttrs, 1, 6);
+ // p
+ testTextAttrs(ID, 6, { }, { }, 6, 7);
+ // plain
+ testTextAttrs(ID, 7, { }, defAttrs, 7, 12);
+ // p and img
+ testTextAttrs(ID, 12, { }, { }, 12, 14);
+ // bold
+ attrs = { "font-weight": kBoldFontWeight };
+ testTextAttrs(ID, 14, attrs, defAttrs, 14, 18);
+ // p
+ testTextAttrs(ID, 18, { }, { }, 18, 19);
+ // bold
+ attrs = { "font-weight": kBoldFontWeight };
+ testTextAttrs(ID, 19, attrs, defAttrs, 19, 23);
+ // p
+ testTextAttrs(ID, 23, { }, { }, 23, 24);
+
+ //////////////////////////////////////////////////////////////////////////
+ // area16, "font-family" tests
+ ID = "area16";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ attrs = { "font-family": kMonospaceFontFamily };
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 4);
+
+ attrs = { };
+ testTextAttrs(ID, 4, attrs, defAttrs, 4, 9);
+
+ attrs = { "font-family": kSerifFontFamily };
+ testTextAttrs(ID, 9, attrs, defAttrs, 9, 13);
+
+ attrs = { };
+ testTextAttrs(ID, 13, attrs, defAttrs, 13, 18);
+
+ attrs = { "font-family": kAbsentFontFamily };
+ testTextAttrs(ID, 18, attrs, defAttrs, 18, 22);
+
+ // bug 1224498 - this fails with 'cursive' fontconfig lookup
+ if (!LINUX) {
+ attrs = { };
+ testTextAttrs(ID, 22, attrs, defAttrs, 22, 27);
+
+ attrs = { "font-family": kCursiveFontFamily };
+ testTextAttrs(ID, 27, attrs, defAttrs, 27, 31);
+
+ attrs = { };
+ testTextAttrs(ID, 31, attrs, defAttrs, 31, 45);
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // area17, "text-decoration" tests
+ ID = "area17";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ attrs = {
+ "text-underline-style": "solid",
+ "text-underline-color": "rgb(0, 0, 0)",
+ };
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 10);
+
+ attrs = {
+ "text-underline-style": "solid",
+ "text-underline-color": "rgb(0, 0, 255)",
+ };
+ testTextAttrs(ID, 10, attrs, defAttrs, 10, 15);
+
+ attrs = {
+ "text-underline-style": "dotted",
+ "text-underline-color": "rgb(0, 0, 0)",
+ };
+ testTextAttrs(ID, 15, attrs, defAttrs, 15, 22);
+
+ attrs = {
+ "text-line-through-style": "solid",
+ "text-line-through-color": "rgb(0, 0, 0)",
+ };
+ testTextAttrs(ID, 22, attrs, defAttrs, 22, 34);
+
+ attrs = {
+ "text-line-through-style": "solid",
+ "text-line-through-color": "rgb(0, 0, 255)",
+ };
+ testTextAttrs(ID, 34, attrs, defAttrs, 34, 39);
+
+ attrs = {
+ "text-line-through-style": "wavy",
+ "text-line-through-color": "rgb(0, 0, 0)",
+ };
+ testTextAttrs(ID, 39, attrs, defAttrs, 39, 44);
+
+ //////////////////////////////////////////////////////////////////////////
+ // area18, "auto-generation text" tests
+ ID = "area18";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ var attrs = {
+ "auto-generated": "true"
+ };
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 3);
+ testTextAttrs(ID, 3, { }, defAttrs, 3, 7);
+ testTextAttrs(ID, 7, attrs, defAttrs, 7, 8);
+
+ //////////////////////////////////////////////////////////////////////////
+ // area19, "HTML5 mark tag" test
+ // text enclosed in mark tag will have a different background color
+ ID = "area19";
+ defAttrs = buildDefaultTextAttrs(ID, "12pt");
+
+ attrs = {};
+ testTextAttrs(ID, 0, attrs, defAttrs, 0, 10);
+
+ tempElem = getNode(ID).firstChild.nextSibling;
+ gComputedStyle = document.defaultView.getComputedStyle(tempElem, "");
+ attrs = { "background-color": gComputedStyle.backgroundColor };
+ testTextAttrs(ID, 11, attrs, defAttrs, 10, 17);
+
+ attrs = {};
+ testTextAttrs(ID, 18, attrs, defAttrs, 17, 28);
+
+ //////////////////////////////////////////////////////////////////////////
+ // area20, "aOffset as -1 (Mozilla Bug 789621)" test
+
+ ID = "area20";
+ defAttrs = buildDefaultTextAttrs(ID, "15pt");
+ testDefaultTextAttrs(ID, defAttrs);
+
+ testTextAttrs(ID, -1, {}, defAttrs, 0, 11);
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body style="font-size: 12pt">
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=345759"
+ title="Implement text attributes">
+ Mozilla Bug 345759
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=473569"
+ title="Restrict text-position to allowed values">
+ Mozilla Bug 473569
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=473576"
+ title="font-family text attribute should expose actual font used">
+ Mozilla Bug 473576
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=523304"
+ title="expose text-underline-color and text-line-through-color text attributes">
+ Mozilla Bug 523304
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=735645"
+ title="expose sub and sup elements in text attributes">
+ Mozilla Bug 735645
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=445516"
+ title="Support auto-generated text attribute on bullet lists">
+ Mozilla Bug 445516
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=789621"
+ title="getTextAttributes doesn't work with magic offsets">
+ Mozilla Bug 789621
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <p id="area1" style="font-size: smaller">Normal <b>Bold</b> Normal</p>
+ <p id="area2" style="font-size: 120%">Normal <b>Bold <i>Italic </i>Bold</b> Normal</p>
+ <p id="area3" style="background-color: blue;">
+ <span style="color: green; background-color: rgb(0, 0, 255)">
+ Green
+ <span style="color: red">but children are red</span>
+ </span><span style="color: green; background-color: rgb(255, 255, 0);">
+ Another green section.
+ </span>
+ </p>
+ <p id="area4">
+ <span style="color: green">
+ Green
+ </span><span style="color: green">
+ Green too
+ <span style="color: red">with red children</span>
+ Green again
+ </span>
+ </p>
+ <!-- Green!*!RedNormal-->
+ <p id="area5">
+ <span style="color: green">Green</span>
+ <img src="../moz.png" alt="image"/>
+ <span style="color: red">Red</span>Normal
+ </p>
+ <p id="area6">
+ This <sup>sentence</sup> has the word
+ <span style="vertical-align:super;">sentence</span> in
+ <sub>superscript</sub> and
+ <span style="vertical-align:sub;">subscript</span> and
+ <span style="vertical-align:20%;">superscript 20%</span> and
+ <span style="vertical-align:-20%;">subscript 20%</span> and
+ <span style="vertical-align:20px;">superscript 20px</span> and
+ <span style="vertical-align:-20px;">subscript 20px</span>
+ </p>
+
+ <p lang="en" id="area7">
+ <span lang="ru">Привет</span>
+ <span style="background-color: blue">Blue BG color</span>
+ <span lang="de">Ich bin/Du bist</span>
+ <span lang="en">
+ Normal
+ <span style="color: magenta">Magenta<b>Bold</b>Magenta</span>
+ </span>
+ </p>
+
+ <p id="area9" style="font-size: smaller">Small
+ <span style="font-size: 120%">bigger</span> smaller
+ <span style="background-color: blue;">background blue</span> normal
+ <span style="font-style: italic;">Different styling</span> normal
+ <span style="font-family: monospace;">Different font</span> normal
+ <span style="text-decoration: underline;">underlined</span> normal
+ <span style="text-decoration: line-through;">strikethrough</span> normal
+ <s>strikethrough</s> normal
+ <strike>strikethrough</strike> normal
+ </p>
+
+ <p id="area10">Normal
+ <span style="font-size: 120%">bigger</span> smaller
+ <span style="background-color: blue;">background blue</span> normal
+ <span style="font-style: italic;">Different styling</span> normal
+ <span style="font-family: monospace;">Different font</span> normal
+ <span style="text-decoration: underline;">underlined</span> normal
+ <span style="text-decoration: line-through;">strikethrough</span> normal
+ </p>
+
+ <p id="area11" style="font-weight: bolder;">
+ <span style="font-weight: bolder;">bolder</span>bolder
+ <span style="font-weight: lighter;">lighter</span>bolder
+ <span style="font-weight: normal;">normal</span>bolder
+ <b>bold</b>bolder
+ <span style="font-weight: 400;">normal</span>bolder
+ <span style="font-weight: 700;">bold</span>bolder
+ <span style="font-weight: bold;">bold</span>bolder
+ <span style="font-weight: 900;">bold</span>bolder
+ </p>
+
+ <p id="area12">hello</p>
+ <p id="area13"></p>
+ <input id="area14">
+
+ <!-- *plain*plain**bold*bold*-->
+ <div id="area15"><p>embed</p>plain<p>embed</p>plain<p>embed</p><img src="../moz.png" alt="image"/><b>bold</b><p>embed</p><b>bold</b><p>embed</p></div>
+
+ <p id="area16" style="font-family: sans-serif;">
+ <span style="font-family: monospace;">text</span>text
+ <span style="font-family: serif;">text</span>text
+ <span style="font-family: BodoniThatDoesntExist;">text</span>text
+ <span style="font-family: Comic Sans MS, cursive;">text</span>text
+ <span style="font-family: sans-serif, fantasy;">text</span>text
+ </p>
+
+ <p id="area17">
+ <span style="text-decoration-line: underline;">underline
+ </span><span style="text-decoration: underline; text-decoration-color: blue;">blue
+ </span><span style="text-decoration: underline; text-decoration-style: dotted;">dotted
+ </span><span style="text-decoration-line: line-through;">linethrough
+ </span><span style="text-decoration: line-through; text-decoration-color: blue;">blue
+ </span><span style="text-decoration: line-through; text-decoration-style: wavy;">wavy
+ </span>
+ </p>
+
+ <ul>
+ <li id="area18" class="gencontent">item</li>
+ </ul>
+
+ <p id="area19">uncolored
+ <mark>colored</mark> uncolored
+ </p>
+
+ <p id="area20" style="font-size: 15pt;">offset test</p>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/textattrs/test_invalid.html b/accessible/tests/mochitest/textattrs/test_invalid.html
new file mode 100644
index 000000000..495db0888
--- /dev/null
+++ b/accessible/tests/mochitest/textattrs/test_invalid.html
@@ -0,0 +1,62 @@
+<html>
+
+<head>
+ <title>Invalid text attribute</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../attributes.js"></script>
+
+ <script type="application/javascript">
+ function doTests()
+ {
+ testDefaultTextAttrs("aria_invalid_empty", {}, true);
+ testDefaultTextAttrs("aria_invalid_true", { "invalid": "true" }, true);
+ testDefaultTextAttrs("aria_invalid_false", { "invalid": "false" }, true);
+ testDefaultTextAttrs("aria_invalid_grammar", { "invalid": "grammar" }, true);
+ testDefaultTextAttrs("aria_invalid_spelling", { "invalid": "spelling" }, true);
+ testDefaultTextAttrs("aria_invalid_erroneous", { "invalid": "true" }, true);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=445510"
+ title="Support ARIA-based text attributes">
+ Mozilla Bug 445510
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="aria_invalid_empty" aria-invalid="">no invalid</div>
+ <div id="aria_invalid_true" aria-invalid="true">invalid:true</div>
+ <div id="aria_invalid_false" aria-invalid="false">invalid:false</div>
+ <div id="aria_invalid_grammar" aria-invalid="grammar">invalid:grammar</div>
+ <div id="aria_invalid_spelling" aria-invalid="spelling">invalid:spelling</div>
+ <div id="aria_invalid_erroneous" aria-invalid="erroneous">invalid:true</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/textcaret/a11y.ini b/accessible/tests/mochitest/textcaret/a11y.ini
new file mode 100644
index 000000000..22d09751d
--- /dev/null
+++ b/accessible/tests/mochitest/textcaret/a11y.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+
+[test_browserui.xul]
+[test_general.html]
diff --git a/accessible/tests/mochitest/textcaret/test_browserui.xul b/accessible/tests/mochitest/textcaret/test_browserui.xul
new file mode 100644
index 000000000..e2be6a464
--- /dev/null
+++ b/accessible/tests/mochitest/textcaret/test_browserui.xul
@@ -0,0 +1,67 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessibility Caret Offset Test.">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../browser.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Tests
+
+ //gA11yEventDumpToConsole = true; // debug
+ //enableLogging("tree,verbose");
+
+ var gQueue = null;
+ function doTests()
+ {
+ gQueue = new eventQueue();
+ gQueue.push(new setCaretOffset(urlbarInput(), -1, urlbarInput()));
+ gQueue.push(new setCaretOffset(urlbarInput(), 0));
+ gQueue.onFinish = function()
+ {
+ closeBrowserWindow();
+ }
+
+ gQueue.invoke();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ openBrowserWindow(doTests, "about:");
+ ]]>
+ </script>
+
+ <vbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=723833"
+ title="IAccessibleText::setCaretOffset on location or search bar causes focus to jump">
+ Bug 723833
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+ </vbox>
+</window>
diff --git a/accessible/tests/mochitest/textcaret/test_general.html b/accessible/tests/mochitest/textcaret/test_general.html
new file mode 100644
index 000000000..69f83959f
--- /dev/null
+++ b/accessible/tests/mochitest/textcaret/test_general.html
@@ -0,0 +1,183 @@
+<html>
+
+<head>
+ <title>Text accessible caret testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ /**
+ * Turn on/off the caret browsing mode.
+ */
+ function turnCaretBrowsing(aIsOn)
+ {
+ var prefs = Components.classes["@mozilla.org/preferences-service;1"].
+ getService(Components.interfaces.nsIPrefBranch);
+ prefs.setBoolPref("accessibility.browsewithcaret", aIsOn);
+ }
+
+ /**
+ * Test caret offset for the given accessible.
+ */
+ function testCaretOffset(aID, aCaretOffset)
+ {
+ var acc = getAccessible(aID, [nsIAccessibleText]);
+ is(acc.caretOffset, aCaretOffset,
+ "Wrong caret offset for " + aID);
+ }
+
+ function testCaretOffsets(aList)
+ {
+ for (var i = 0; i < aList.length; i++)
+ testCaretOffset(aList[0][0], aList[0][1]);
+ }
+
+ function queueTraversalList(aList, aFocusNode)
+ {
+ for (var i = 0 ; i < aList.length; i++) {
+ var node = aList[i].DOMPoint[0];
+ var nodeOffset = aList[i].DOMPoint[1];
+
+ var textAcc = aList[i].point[0];
+ var textOffset = aList[i].point[1];
+ var textList = aList[i].pointList;
+ var invoker =
+ new moveCaretToDOMPoint(textAcc, node, nodeOffset, textOffset,
+ ((i == 0) ? aFocusNode : null),
+ testCaretOffsets.bind(null, textList))
+ gQueue.push(invoker);
+ }
+ }
+
+ /**
+ * Do tests.
+ */
+ var gQueue = null;
+
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ function doTests()
+ {
+ turnCaretBrowsing(true);
+
+ // test caret offsets
+ testCaretOffset(document, 16);
+ testCaretOffset("textbox", -1);
+ testCaretOffset("textarea", -1);
+ testCaretOffset("p", -1);
+
+ // test caret move events and caret offsets
+ gQueue = new eventQueue();
+
+ gQueue.push(new setCaretOffset("textbox", 1, "textbox"));
+ gQueue.push(new setCaretOffset("link", 1, "link"));
+ gQueue.push(new setCaretOffset("heading", 1, document));
+
+ // a*b*c
+ var p2Doc = getNode("p2_container").contentDocument;
+ var traversalList = [
+ { // before 'a'
+ DOMPoint: [ getNode("p2", p2Doc).firstChild, 0 ],
+ point: [ getNode("p2", p2Doc), 0 ],
+ pointList: [ [ p2Doc, 0 ] ]
+ },
+ { // after 'a' (before anchor)
+ DOMPoint: [ getNode("p2", p2Doc).firstChild, 1 ],
+ point: [ getNode("p2", p2Doc), 1 ],
+ pointList: [ [ p2Doc, 0 ] ]
+ },
+ { // before 'b' (inside anchor)
+ DOMPoint: [ getNode("p2_a", p2Doc).firstChild, 0 ],
+ point: [ getNode("p2_a", p2Doc), 0 ],
+ pointList: [
+ [ getNode("p2", p2Doc), 1 ],
+ [ p2Doc, 0 ]
+ ]
+ },
+ { // after 'b' (inside anchor)
+ DOMPoint: [ getNode("p2_a", p2Doc).firstChild, 1 ],
+ point: [ getNode("p2_a", p2Doc), 1 ],
+ pointList: [
+ [ getNode("p2", p2Doc), 1 ] ,
+ [ p2Doc, 0 ]
+ ]
+ },
+ { // before 'c' (after anchor)
+ DOMPoint: [ getNode("p2", p2Doc).lastChild, 0 ],
+ point: [ getNode("p2", p2Doc), 2 ],
+ pointList: [ [ p2Doc, 0 ] ]
+ },
+ { // after 'c'
+ DOMPoint: [ getNode("p2", p2Doc).lastChild, 1 ],
+ point: [ getNode("p2", p2Doc), 3 ],
+ pointList: [ [ p2Doc, 0 ] ]
+ }
+ ];
+ queueTraversalList(traversalList, getNode("p2", p2Doc));
+
+ gQueue.onFinish = function()
+ {
+ turnCaretBrowsing(false);
+ }
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=448744"
+ title="caretOffset should return -1 if the system caret is not currently with in that particular object">
+ Bug 448744
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=524115"
+ title="HyperText accessible should get focus when the caret is positioned inside of it, text is changed or copied into clipboard by ATs">
+ Bug 524115
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=546068"
+ title="Position is not being updated when atk_text_set_caret_offset is used">
+ Bug 546068
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=672717"
+ title="Broken caret when moving into/out of embedded objects with right arrow">
+ Bug 672717
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=725581"
+ title="caretOffset for textarea should be -1 when textarea doesn't have a focus">
+ Bug 725581
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="textbox" value="hello"/>
+ <textarea id="textarea">text<br>text</textarea>
+ <p id="p" contentEditable="true"><span>text</span><br/>text</p>
+ <a id="link" href="about:">about mozilla</a>
+ <h5 id="heading">heading</h5>
+ <iframe id="p2_container"
+ src="data:text/html,<p id='p2' contentEditable='true'>a<a id='p2_a' href='mozilla.org'>b</a>c</p>"></iframe>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/textrange/a11y.ini b/accessible/tests/mochitest/textrange/a11y.ini
new file mode 100644
index 000000000..4e610c61f
--- /dev/null
+++ b/accessible/tests/mochitest/textrange/a11y.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/moz.png
+
+[test_general.html]
+[test_selection.html]
diff --git a/accessible/tests/mochitest/textrange/test_general.html b/accessible/tests/mochitest/textrange/test_general.html
new file mode 100644
index 000000000..21a758e17
--- /dev/null
+++ b/accessible/tests/mochitest/textrange/test_general.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Text Range tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+ <script type="application/javascript"
+ src="../layout.js"></script>
+ <script type="application/javascript">
+
+ function doTest()
+ {
+ // enclosingRange
+ var input = getAccessible("input", [ nsIAccessibleText ]);
+ testTextRange(input.enclosingRange, "enclosing range for 'input'",
+ input, 0, input, 5, "hello", input);
+
+ var ta = getAccessible("textarea", [ nsIAccessibleText ]);
+ testTextRange(ta.enclosingRange, "enclosing range for 'textarea'",
+ ta, 0, ta, 5, "hello", textarea);
+
+ var iframeDocNode = getNode("iframe").contentDocument;
+ var iframeDoc = getAccessible(iframeDocNode, [ nsIAccessibleText ]);
+ testTextRange(iframeDoc.enclosingRange, "enclosing range for iframe doc",
+ iframeDoc, 0, iframeDoc, 1, "hello",
+ iframeDoc, [ getNode("p", iframeDocNode) ]);
+
+ // getRangeByChild
+ var docacc = getAccessible(document, [ nsIAccessibleText ]);
+ var p1 = getAccessible("p1");
+ var p1Range = docacc.getRangeByChild(p1);
+ testTextRange(p1Range, "range by 'p1' child",
+ p1, 0, "p1", 11, "text text",
+ p1, ["p1_img"]);
+
+ testTextRange(docacc.getRangeByChild(getAccessible("p1_img")),
+ "range by 'p1_img' child",
+ "p1", 5, "p1", 5, "",
+ "p1", ["p1_img"]);
+
+ var p2 = getAccessible("p2");
+ var p2Range = docacc.getRangeByChild(p2);
+ testTextRange(p2Range, "range by 'p2' child",
+ p2, 0, "p2", 11, "text link text",
+ p2, ["p2_a"]);
+
+ testTextRange(docacc.getRangeByChild(getAccessible("p2_a")),
+ "range by 'p2_a' child",
+ "p2_a", 0, "p2_a", 5, "link",
+ "p2_a", ["p2_img"]);
+
+ // getRangeAtPoint
+ getNode("p2_a").scrollIntoView(true);
+ var [x, y] = getPos("p2_a");
+ testTextRange(docacc.getRangeAtPoint(x + 1, y + 1),
+ "range at 'p2_a' top-left edge",
+ "p2_a", 0, "p2_a", 0, "",
+ "p2_a");
+
+ // TextRange::compare
+ ok(input.enclosingRange.compare(input.enclosingRange),
+ "input enclosing ranges should be equal");
+
+ ok(!input.enclosingRange.compare(ta.enclosingRange),
+ "input and textarea enclosing ranges can't be equal");
+
+ // TextRange::compareEndPoints
+ var res = p1Range.compareEndPoints(EndPoint_End, p2Range, EndPoint_Start);
+ is(res, -1, "p1 range must be lesser with p2 range");
+
+ res = p2Range.compareEndPoints(EndPoint_Start, p1Range, EndPoint_End);
+ is(res, 1, "p2 range must be greater with p1 range");
+
+ res = p1Range.compareEndPoints(EndPoint_Start, p1Range, EndPoint_Start);
+ is(res, 0, "p1 range must be equal with p1 range");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Implement Text accessible text range methods"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=975065">Bug 975065</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input id="input" value="hello">
+ <textarea id="textarea">hello</textarea>
+ <iframe id="iframe" src="data:text/html,<html><body><p id='p'>hello</p></body></html>"></iframe>
+ <p id="p1">text <img id="p1_img", src="../moz.png"> text</p>
+ <p id="p2">text <a id="p2_a" href="www">link<img id="p2_img", src="../moz.png"></a> text</p>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/textrange/test_selection.html b/accessible/tests/mochitest/textrange/test_selection.html
new file mode 100644
index 000000000..a7c79091f
--- /dev/null
+++ b/accessible/tests/mochitest/textrange/test_selection.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Text Range selection tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../text.js"></script>
+ <script type="application/javascript"
+ src="../layout.js"></script>
+ <script type="application/javascript">
+
+ function doTest()
+ {
+ var sel = window.getSelection();
+ var p = getNode("p1");
+ var a = getNode("p2_a");
+
+ var range = document.createRange();
+ sel.addRange(range);
+
+ // the accessible is contained by the range
+ range.selectNode(p);
+
+ var a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
+ var a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
+
+ testTextRange(a11yrange, "selection range #1", document, 3, document, 4);
+
+ ok(a11yrange.crop(getAccessible(a)), "Range failed to crop #1.");
+ testTextRange(a11yrange, "cropped range #1", a, 0, a, 5);
+
+ // the range is contained by the accessible
+ range.selectNode(a);
+ var a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
+ var a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
+
+ testTextRange(a11yrange, "selection range #2", p, 5, p, 6);
+
+ ok(a11yrange.crop(getAccessible(p)), "Range failed to crop #2.");
+ testTextRange(a11yrange, "cropped range #2", p, 5, p, 6);
+
+ // the range starts before the accessible and ends inside it
+ range.setStart(p, 0);
+ range.setEndAfter(a.firstChild, 4);
+ var a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
+ var a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
+
+ testTextRange(a11yrange, "selection range #3", p, 0, a, 4);
+
+ ok(a11yrange.crop(getAccessible(a)), "Range failed to crop #3.");
+ testTextRange(a11yrange, "cropped range #3", a, 0, a, 4);
+
+ // the range starts inside the accessible and ends after it
+ range.setStart(a.firstChild, 1);
+ range.setEndAfter(p);
+ var a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
+ var a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
+
+ testTextRange(a11yrange, "selection range #4", a, 1, document, 4);
+
+ ok(a11yrange.crop(getAccessible(a)), "Range failed to crop #4.");
+ testTextRange(a11yrange, "cropped range #4", a, 1, a, 5);
+
+ // the range ends before the accessible
+ range.setStart(p.firstChild, 0);
+ range.setEnd(p.firstChild, 4);
+ var a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
+ var a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
+
+ testTextRange(a11yrange, "selection range #5", p, 0, p, 4);
+ ok(!a11yrange.crop(getAccessible(a)), "Crop #5 succeeded while it shouldn't");
+
+ // the range starts after the accessible
+ range.setStart(p.lastChild, 0);
+ range.setEnd(p.lastChild, 4);
+ var a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
+ var a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
+
+ testTextRange(a11yrange, "selection range #6", p, 6, p, 10);
+
+ ok(!a11yrange.crop(getAccessible(a)), "Crop #6 succeeded while it shouldn't");
+
+ // crop a range by a table
+ range.selectNode(getNode("c2"));
+ var a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
+ var a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
+
+ testTextRange(a11yrange, "selection range #7", document, 4, document, 5);
+
+ ok(a11yrange.crop(getAccessible("table")), "Range failed to crop #7.");
+ testTextRange(a11yrange, "cropped range #7", "c2", 5, "c2", 6);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Implement IAccessible2_3::selectionRanges"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1233118">Bug 1233118</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <p id="p1">text <a id="p2_a" href="www">link<img id="p2_img", src="../moz.png"></a> text</p>
+
+ <div id="c2">start<table id="table"><tr><td>cell</td></tr></table>end</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/textselection/a11y.ini b/accessible/tests/mochitest/textselection/a11y.ini
new file mode 100644
index 000000000..6581af56d
--- /dev/null
+++ b/accessible/tests/mochitest/textselection/a11y.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+
+[test_general.html]
+[test_userinput.html]
diff --git a/accessible/tests/mochitest/textselection/test_general.html b/accessible/tests/mochitest/textselection/test_general.html
new file mode 100644
index 000000000..87d95eaf0
--- /dev/null
+++ b/accessible/tests/mochitest/textselection/test_general.html
@@ -0,0 +1,221 @@
+<html>
+
+<head>
+ <title>Text selection testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ /**
+ * Invokers
+ */
+ function addSelection(aID, aStartOffset, aEndOffset)
+ {
+ this.hyperTextNode = getNode(aID);
+ this.hyperText = getAccessible(aID, [ nsIAccessibleText ]);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID)
+ ];
+
+ this.invoke = function addSelection_invoke()
+ {
+ this.hyperText.addSelection(aStartOffset, aEndOffset);
+ }
+
+ this.finalCheck = function addSelection_finalCheck()
+ {
+ is(this.hyperText.selectionCount, 1,
+ "addSelection: Wrong selection count for " + aID);
+ var startOffset = {}, endOffset = {};
+ this.hyperText.getSelectionBounds(0, startOffset, endOffset);
+
+ is(startOffset.value, aStartOffset,
+ "addSelection: Wrong start offset for " + aID);
+ is(endOffset.value, aEndOffset,
+ "addSelection: Wrong end offset for " + aID);
+ }
+
+ this.getID = function addSelection_getID()
+ {
+ return "nsIAccessibleText::addSelection test for " + aID;
+ }
+ }
+
+ function changeSelection(aID, aStartOffset, aEndOffset)
+ {
+ this.hyperTextNode = getNode(aID);
+ this.hyperText = getAccessible(aID, [ nsIAccessibleText ]);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID)
+ ];
+
+ this.invoke = function changeSelection_invoke()
+ {
+ this.hyperText.setSelectionBounds(0, aStartOffset, aEndOffset);
+ }
+
+ this.finalCheck = function changeSelection_finalCheck()
+ {
+ is(this.hyperText.selectionCount, 1,
+ "setSelectionBounds: Wrong selection count for " + aID);
+ var startOffset = {}, endOffset = {};
+ this.hyperText.getSelectionBounds(0, startOffset, endOffset);
+
+ is(startOffset.value, aStartOffset,
+ "setSelectionBounds: Wrong start offset for " + aID);
+ is(endOffset.value, aEndOffset,
+ "setSelectionBounds: Wrong end offset for " + aID);
+ }
+
+ this.getID = function changeSelection_getID()
+ {
+ return "nsIAccessibleText::setSelectionBounds test for " + aID;
+ }
+ }
+
+ function removeSelection(aID)
+ {
+ this.hyperText = getAccessible(aID, [ nsIAccessibleText ]);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, document)
+ ];
+
+ this.invoke = function removeSelection_invoke()
+ {
+ this.hyperText.removeSelection(0);
+ }
+
+ this.finalCheck = function removeSelection_finalCheck()
+ {
+ is(this.hyperText.selectionCount, 0,
+ "removeSelection: Wrong selection count for " + aID);
+ }
+
+ this.getID = function removeSelection_getID()
+ {
+ return "nsIAccessibleText::removeSelection test for " + aID;
+ }
+ }
+
+ function changeDOMSelection(aID, aNodeID1, aNodeOffset1,
+ aNodeID2, aNodeOffset2,
+ aTests)
+ {
+ this.hyperText = getAccessible(aID, [ nsIAccessibleText ]);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID)
+ ];
+
+ this.invoke = function changeDOMSelection_invoke()
+ {
+ var sel = window.getSelection();
+ var range = document.createRange();
+ range.setStart(getNode(aNodeID1), aNodeOffset1);
+ range.setEnd(getNode(aNodeID2), aNodeOffset2);
+ sel.addRange(range);
+ }
+
+ this.finalCheck = function changeDOMSelection_finalCheck()
+ {
+ for (var i = 0; i < aTests.length; i++) {
+ var text = getAccessible(aTests[i][0], nsIAccessibleText);
+ is(text.selectionCount, 1,
+ "setSelectionBounds: Wrong selection count for " + aID);
+ var startOffset = {}, endOffset = {};
+ text.getSelectionBounds(0, startOffset, endOffset);
+
+ is(startOffset.value, aTests[i][1],
+ "setSelectionBounds: Wrong start offset for " + aID);
+ is(endOffset.value, aTests[i][2],
+ "setSelectionBounds: Wrong end offset for " + aID);
+ }
+ }
+
+ this.getID = function changeDOMSelection_getID()
+ {
+ return "DOM selection change for " + aID;
+ }
+ }
+
+ function onfocusEventSeq(aID)
+ {
+ var caretMovedChecker =
+ new invokerChecker(EVENT_TEXT_CARET_MOVED, aID);
+ var selChangedChecker =
+ new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID);
+ selChangedChecker.unexpected = true;
+
+ return [ caretMovedChecker, selChangedChecker ];
+ }
+
+ /**
+ * Do tests
+ */
+
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new addSelection("paragraph", 1, 3));
+ gQueue.push(new changeSelection("paragraph", 2, 4));
+ gQueue.push(new removeSelection("paragraph"));
+
+ gQueue.push(new synthFocus("textbox", onfocusEventSeq("textbox")));
+ gQueue.push(new changeSelection("textbox", 1, 3));
+
+ gQueue.push(new synthFocus("textarea", onfocusEventSeq("textarea")));
+ gQueue.push(new changeSelection("textarea", 1, 3));
+
+ gQueue.push(new changeDOMSelection("c1", "c1_span1", 0, "c1_span2", 0,
+ [["c1", 2, 2]]));
+ gQueue.push(new changeDOMSelection("c2", "c2", 0, "c2_div2", 1,
+ [["c2", 0, 3], ["c2_div2", 0, 2]]));
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=688126"
+ title="nsIAccessibleText::setSelectionBounds doesn't fire text selection changed events in some cases">
+ Bug 688126
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=688124"
+ title="no text selection changed event when selection is removed">
+ Bug 688124
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <p id="paragraph">hello</p>
+ <input id="textbox" value="hello"/>
+ <textarea id="textarea">hello</textarea>
+ <div id="c1">hi<span id="c1_span1"></span><span id="c1_span2"></span>hi</div>
+ <div id="c2">hi<div id="c2_div2">hi</div></div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/textselection/test_userinput.html b/accessible/tests/mochitest/textselection/test_userinput.html
new file mode 100644
index 000000000..1f7127866
--- /dev/null
+++ b/accessible/tests/mochitest/textselection/test_userinput.html
@@ -0,0 +1,95 @@
+<html>
+
+<head>
+ <title>Text selection by user input</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ /**
+ * Invokers
+ */
+ function synthTabAndCheckPrevTabbed(aID, aPrevID)
+ {
+ this.__proto__ = new synthTab(aID, new focusChecker(aID));
+
+ this.finalCheck = function changeSelection_finalCheck()
+ {
+ var prevTabbed = getAccessible(aPrevID, [ nsIAccessibleText ]);
+ is(prevTabbed.selectionCount, 0,
+ "Wrong selection count for " + aPrevID);
+
+ var exceptionCaught = false;
+ try {
+ var startOffsetObj = {}, endOffsetObj = {};
+ prevTabbed.getSelectionBounds(0, startOffsetObj, endOffsetObj);
+ } catch (e) {
+ exceptionCaught = true;
+ }
+
+ ok(exceptionCaught, "No selection was expected for " + aPrevID);
+ }
+
+ this.getID = function changeSelection_getID()
+ {
+ return "Hidden selection check for " + aPrevID;
+ }
+ }
+
+ /**
+ * Do tests
+ */
+
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ // Tab to 't2' and then tab out it: it must has no selection.
+ gQueue.push(new synthFocus("t1"));
+ gQueue.push(new synthTab("t2", new focusChecker("t2")));
+ gQueue.push(new synthTabAndCheckPrevTabbed("t3", "t2"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=440590"
+ title="Text selection information is not updated when HTML and XUL entries lose focus">
+ Bug 440590
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input type="text" id="t1" maxlength="3" size="3" value="1">
+ <input type="text" id="t2" maxlength="3" size="3" value="1">
+ <input type="text" id="t3" maxlength="3" size="3" value="1">
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/a11y.ini b/accessible/tests/mochitest/tree/a11y.ini
new file mode 100644
index 000000000..c43e4552e
--- /dev/null
+++ b/accessible/tests/mochitest/tree/a11y.ini
@@ -0,0 +1,51 @@
+[DEFAULT]
+support-files =
+ dockids.html
+ wnd.xul
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/formimage.png
+ !/accessible/tests/mochitest/letters.gif
+ !/accessible/tests/mochitest/moz.png
+ !/accessible/tests/mochitest/tree/wnd.xul
+ !/dom/media/test/bug461281.ogg
+
+[test_applicationacc.xul]
+skip-if = true # Bug 561508
+[test_aria_globals.html]
+[test_aria_grid.html]
+[test_aria_imgmap.html]
+[test_aria_list.html]
+[test_aria_menu.html]
+[test_aria_owns.html]
+[test_aria_presentation.html]
+[test_aria_table.html]
+[test_brokencontext.html]
+[test_button.xul]
+[test_canvas.html]
+[test_combobox.xul]
+[test_cssflexbox.html]
+[test_cssoverflow.html]
+[test_dochierarchy.html]
+[test_dockids.html]
+[test_filectrl.html]
+[test_formctrl.html]
+skip-if = buildapp == "mulet"
+[test_formctrl.xul]
+[test_gencontent.html]
+[test_groupbox.xul]
+[test_iframe.html]
+[test_img.html]
+[test_invalid_img.xhtml]
+[test_invalidationlist.html]
+[test_list.html]
+[test_map.html]
+[test_media.html]
+skip-if = buildapp == "mulet"
+[test_select.html]
+[test_tabbox.xul]
+[test_tabbrowser.xul]
+[test_table.html]
+[test_tree.xul]
+[test_txtcntr.html]
+[test_txtctrl.html]
+[test_txtctrl.xul]
diff --git a/accessible/tests/mochitest/tree/dockids.html b/accessible/tests/mochitest/tree/dockids.html
new file mode 100644
index 000000000..c59ae3267
--- /dev/null
+++ b/accessible/tests/mochitest/tree/dockids.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<html>
+ <head>
+ <link rel="next" href="http://www.mozilla.org">
+ <style>
+ head, link, a { display: block; }
+ link:after { content: "Link to " attr(href); }
+ </style>
+ <script>
+ window.onload = function() {
+ document.documentElement.appendChild(document.createElement("input"));
+
+ var l = document.createElement("link");
+ l.href = "http://www.mozilla.org";
+ l.textContent = "Another ";
+ document.documentElement.appendChild(l);
+
+ l = document.createElement("a");
+ l.href = "http://www.mozilla.org";
+ l.textContent = "Yet another link to mozilla";
+ document.documentElement.appendChild(l);
+ }
+ </script>
+ </head>
+ <body>
+ Hey, I'm a <body> with three links that are not inside me and an input
+ that's not inside me.
+ </body>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_applicationacc.xul b/accessible/tests/mochitest/tree/test_applicationacc.xul
new file mode 100644
index 000000000..5811d00d3
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_applicationacc.xul
@@ -0,0 +1,74 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible Application Accessible hierarchy tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // Note: bug 560239 can be tested if this test runs in standalone mode only.
+
+ var gURL = "../tree/wnd.xul"
+ var gWnd = window.openDialog(gURL, "wnd", "chrome,width=600,height=600");
+
+ function doTest()
+ {
+ // Application accessible should contain two root document accessibles,
+ // one is for browser window, another one is for open dialog window.
+ var accTree = {
+ role: ROLE_APP_ROOT,
+ children: [
+ {
+ role: ROLE_CHROME_WINDOW,
+ name: "Accessibility Chrome Test Harness - Minefield"
+ },
+ {
+ role: ROLE_CHROME_WINDOW,
+ name: "Empty Window"
+ }
+ ]
+ };
+ testAccessibleTree(getApplicationAccessible(), accTree);
+
+ gWnd.close();
+
+ SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ // We need to open dialog window before accessibility is started.
+ addLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=560239"
+ title="no children of application accessible for windows open before accessibility was started">
+ Mozilla Bug 560239
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/tree/test_aria_globals.html b/accessible/tests/mochitest/tree/test_aria_globals.html
new file mode 100644
index 000000000..771640fcc
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_aria_globals.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test Global ARIA States and Accessible Creation</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ var globalIds = [
+ "atomic",
+ "busy",
+ "controls",
+ "describedby",
+ "disabled",
+ "dropeffect",
+ "flowto",
+ "grabbed",
+ "haspopup",
+ "hidden",
+ "invalid",
+ "label",
+ "labelledby",
+ "live",
+ "owns",
+ "relevant"
+ ];
+
+ // Elements having ARIA global state or properties or referred by another
+ // element must be accessible.
+ ok(isAccessible("pawn"),
+ "Must be accessible because referred by another element.");
+
+ for (var idx = 0; idx < globalIds.length; idx++) {
+ ok(isAccessible(globalIds[idx]),
+ "Must be accessible becuase of " + "aria-" + globalIds[idx] +
+ " presence");
+ }
+
+ // Unfocusable elements, having ARIA global state or property with a valid
+ // IDREF value, and an inherited presentation role. A generic accessible
+ // is created (to prevent table cells text jamming).
+ ok(!isAccessible("td_nothing", nsIAccessibleTableCell),
+ "inherited presentation role takes a place");
+
+ for (var idx = 0; idx < globalIds.length; idx++) {
+ ok(isAccessible("td_" + globalIds[idx]),
+ "Inherited presentation role must be ignored becuase of " +
+ "aria-" + globalIds[idx] + " presence");
+ }
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Update universal ARIA attribute support to latest spec"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=551978">
+ Mozilla Bug 551978
+ </a>
+ <a target="_blank"
+ title="Presentational table related elements referred or having global ARIA attributes must be accessible"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=809751">
+ Mozilla Bug 809751
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- Test that global aria states and properties are enough to cause the
+ creation of accessible objects -->
+ <div id="global_aria_states_and_props" role="group">
+ <span id="pawn"></span>
+ <span id="atomic" aria-atomic="true"></span>
+ <span id="busy" aria-busy="false"></span>
+ <span id="controls" aria-controls="pawn"></span>
+ <span id="describedby" aria-describedby="pawn"></span>
+ <span id="disabled" aria-disabled="true"></span>
+ <span id="dropeffect" aria-dropeffect="move"></span>
+ <span id="flowto" aria-flowto="pawn"></span>
+ <span id="grabbed" aria-grabbed="false"></span>
+ <span id="haspopup" aria-haspopup="false"></span>
+ <span id="hidden" aria-hidden="true"></span>
+ <span id="invalid" aria-invalid="false"></span>
+ <span id="label" aria-label="hi"></span>
+ <span id="labelledby" aria-labelledby="label"></span>
+ <span id="live" aria-live="polite"></span>
+ <span id="owns" aria-owns="pawn"></span>
+ <span id="relevant" aria-relevant="additions"></span>
+ </div>
+
+ <table role="presentation">
+ <tr>
+ <td id="td_nothing"></td>
+ <td id="td_atomic" aria-atomic="true"></td>
+ <td id="td_busy" aria-busy="false"></td>
+ <td id="td_controls" aria-controls="pawn"></td>
+ <td id="td_describedby" aria-describedby="pawn"></td>
+ <td id="td_disabled" aria-disabled="true"></td>
+ <td id="td_dropeffect" aria-dropeffect="move"></td>
+ <td id="td_flowto" aria-flowto="pawn"></td>
+ <td id="td_grabbed" aria-grabbed="false"></td>
+ <td id="td_haspopup" aria-haspopup="false"></td>
+ <td id="td_hidden" aria-hidden="true"></td>
+ <td id="td_invalid" aria-invalid="false"></td>
+ <td id="td_label" aria-label="hi"></td>
+ <td id="td_labelledby" aria-labelledby="label"></td>
+ <td id="td_live" aria-live="polite"></td>
+ <td id="td_owns" aria-owns="pawn"></td>
+ <td id="td_relevant" aria-relevant="additions"></td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_aria_grid.html b/accessible/tests/mochitest/tree/test_aria_grid.html
new file mode 100644
index 000000000..b7fa91cee
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_aria_grid.html
@@ -0,0 +1,279 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML table tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // grid having rowgroups
+
+ var accTree =
+ { TABLE: [
+ { GROUPING: [
+ { ROW: [
+ { GRID_CELL: [
+ { TEXT_LEAF: [ ] }
+ ] }
+ ] }
+ ] },
+ ] };
+
+ testAccessibleTree("grid", accTree);
+
+ //////////////////////////////////////////////////////////////////////////
+ // crazy grids (mad mix of ARIA and HTML tables)
+
+ accTree = {
+ role: ROLE_TABLE,
+ children: [
+ { // div@role="row"
+ role: ROLE_ROW,
+ tagName: "DIV",
+ children: [
+ { // caption text leaf
+ role: ROLE_TEXT_LEAF,
+ name: "caption",
+ children: [ ]
+ },
+ { // th generic accessible
+ role: ROLE_TEXT_CONTAINER,
+ children: [
+ { // th text leaf
+ role: ROLE_TEXT_LEAF,
+ name: "header1",
+ children: [ ]
+ }
+ ]
+ },
+ { // td@role="columnheader"
+ role: ROLE_COLUMNHEADER,
+ name: "header2",
+ children: [ { TEXT_LEAF: [ ] } ]
+ }
+ ]
+ }
+ ]
+ };
+ testAccessibleTree("crazy_grid1", accTree);
+
+ accTree = {
+ role: ROLE_TABLE,
+ children: [
+ { // tr@role="row"
+ role: ROLE_ROW,
+ tagName: "TR",
+ children: [
+ { // td generic accessible
+ role: ROLE_TEXT_CONTAINER,
+ children: [
+ { // td text leaf
+ role: ROLE_TEXT_LEAF,
+ name: "cell1",
+ children: [ ]
+ }
+ ]
+ },
+ { // td@role="gridcell"
+ role: ROLE_GRID_CELL,
+ name: "cell2",
+ children: [ { TEXT_LEAF: [ ] } ]
+ }
+ ]
+ }
+ ]
+ };
+ testAccessibleTree("crazy_grid2", accTree);
+
+ accTree = {
+ role: ROLE_TABLE,
+ children: [
+ { // div@role="row"
+ role: ROLE_ROW,
+ children: [
+ { // div@role="gridcell"
+ role: ROLE_GRID_CELL,
+ children: [
+ { // td generic accessible
+ role: ROLE_TEXT_CONTAINER,
+ children: [
+ { // text leaf from presentational table
+ role: ROLE_TEXT_LEAF,
+ name: "cell3",
+ children: [ ]
+ }
+ ]
+ },
+ ]
+ }
+ ]
+ }
+ ]
+ };
+ testAccessibleTree("crazy_grid3", accTree);
+
+ accTree = {
+ role: ROLE_TABLE,
+ children: [
+ { // div@role="row"
+ role: ROLE_ROW,
+ children: [
+ { // div@role="gridcell"
+ role: ROLE_GRID_CELL,
+ children: [
+ { // table
+ role: ROLE_TABLE,
+ children: [
+ { // tr
+ role: ROLE_ROW,
+ children: [
+ { // td
+ role: ROLE_CELL,
+ children: [
+ { // caption text leaf of presentational table
+ role: ROLE_TEXT_LEAF,
+ name: "caption",
+ children: [ ]
+ },
+ { // td generic accessible
+ role: ROLE_TEXT_CONTAINER,
+ children: [
+ { // td text leaf of presentational table
+ role: ROLE_TEXT_LEAF,
+ name: "cell4",
+ children: [ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ };
+
+ testAccessibleTree("crazy_grid4", accTree);
+
+ //////////////////////////////////////////////////////////////////////////
+ // grids that could contain whitespace accessibles but shouldn't.
+
+ var accTree =
+ { TREE_TABLE: [
+ { ROW: [
+ { GRID_CELL: [
+ { TEXT_LEAF: [ ] }
+ ] },
+ { GRID_CELL: [
+ { TEXT_LEAF: [ ] }
+ ] },
+ { GRID_CELL: [
+ { TEXT_LEAF: [ ] }
+ ] }
+ ] },
+ ] };
+
+ testAccessibleTree("whitespaces-grid", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Support ARIA role rowgroup"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=525909">
+ Mozilla Bug 525909
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="grid" role="grid">
+ <div role="rowgroup">
+ <div role="row">
+ <div role="gridcell">cell</div>
+ </div>
+ </div>
+ </div>
+
+ <div id="crazy_grid1" role="grid">
+ <div role="row">
+ <table role="presentation">
+ <caption>caption</caption>
+ <tr>
+ <th>header1</th>
+ <td role="columnheader">header2</td>
+ </tr>
+ </table>
+ </div>
+ </div>
+
+ <div id="crazy_grid2" role="grid">
+ <table role="presentation">
+ <tr role="row">
+ <td id="ct_cell1">cell1</td>
+ <td role="gridcell">cell2</td>
+ </tr>
+ </table>
+ </div>
+
+ <div id="crazy_grid3" role="grid">
+ <div role="row">
+ <div role="gridcell">
+ <table role="presentation">
+ <tr>
+ <td>cell3</td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ </div>
+
+ <div id="crazy_grid4" role="grid">
+ <div role="row">
+ <div role="gridcell">
+ <table>
+ <tr>
+ <td>
+ <table role="presentation">
+ <caption>caption</caption>
+ <tr><td>cell4</td></tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ </div>
+
+ <div role="treegrid" id="whitespaces-grid">
+ <div role="row" aria-selected="false" tabindex="-1">
+ <span role="gridcell">03:30PM-04:30PM</span>
+ <span role="gridcell" style="font-weight:bold;">test</span>
+ <span role="gridcell">a user1</span>
+ </div>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_aria_imgmap.html b/accessible/tests/mochitest/tree/test_aria_imgmap.html
new file mode 100644
index 000000000..30f2eafe6
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_aria_imgmap.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test usemap elements and ARIA</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ //gA11yEventDumpToConsole = true;
+ function doPreTest()
+ {
+ waitForImageMap("imagemap", doTest);
+ }
+
+ function doTest()
+ {
+ var accTree = {
+ role: ROLE_IMAGE_MAP,
+ children: [
+ {
+ role: ROLE_ENTRY,
+ name: "first name"
+ },
+ {
+ role: ROLE_ENTRY,
+ name: "last name"
+ },
+ {
+ role: ROLE_RADIOBUTTON,
+ name: "male"
+ },
+ {
+ role: ROLE_RADIOBUTTON,
+ name: "female"
+ },
+ {
+ role: ROLE_CHECKBUTTON,
+ name: "have bike"
+ },
+ {
+ role: ROLE_COMBOBOX,
+ name: "bike model"
+ },
+ {
+ role: ROLE_CHECKBUTTON,
+ name: "have car"
+ },
+ {
+ role: ROLE_CHECKBUTTON,
+ name: "have airplane"
+ },
+ {
+ role: ROLE_PUSHBUTTON,
+ name: "submit"
+ }
+ ]
+ };
+
+ // Test image map tree structure, roles, and names.
+ testAccessibleTree("imagemap", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doPreTest);
+ </script>
+
+</head>
+<body>
+
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=548291"
+ title="Accessible tree of ARIA image maps">
+Mozilla Bug 548291
+</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+
+<img id="imagemap" src="../formimage.png" width="219" height="229" border="0" usemap="#ariaMap">
+<map id="ariaMap" name="ariaMap">
+ <area id="t1" role="textbox" shape="rect" tabindex="0" alt="" title="first name" coords="4,20,108,48" href="#" />
+ <area id="t2" role="textbox" shape="rect" alt="" title="last name" coords="111,21,215,50" href="#" />
+ <area id="rb1" role="radio" aria-checked="true" shape="circle" alt="" title="male" coords="60,75,11" href="#" />
+ <area id="rb2" role="radio" shape="circle" alt="" title="female" coords="73,94,11" href="#" />
+ <area id="cb1" role="checkbox" aria-checked="true" shape="rect" alt="" title="have bike" coords="95,123,118,145" href="#" />
+ <area id="cbox" role="combobox" shape="rect" alt="" title="bike model" coords="120,124,184,146" href="#" />
+ <area id="cb2" role="checkbox" shape="rect" alt="" title="have car" coords="90,145,114,164" href="#" />
+ <area id="cb3" role="checkbox" shape="rect" alt="" title="have airplane" coords="130,163,152,184" href="#" />
+ <area id="b1" role="button" shape="rect" alt="" title="submit" coords="4,198,67,224" href="#" />
+</map>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_aria_list.html b/accessible/tests/mochitest/tree/test_aria_list.html
new file mode 100644
index 000000000..b53962709
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_aria_list.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>ARIA lists</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // list
+
+ var accTree =
+ { LIST: [
+ { LISTITEM: [
+ { TEXT_LEAF: [ ] }
+ ] }
+ ] };
+
+ testAccessibleTree("list", accTree);
+
+ //////////////////////////////////////////////////////////////////////////
+ // crazy list (mad mix of ARIA and HTML)
+
+ accTree = { // div@role="list"
+ role: ROLE_LIST,
+ children: [
+ { // li
+ role: ROLE_TEXT_CONTAINER,
+ children: [
+ { // li text leaf
+ role: ROLE_TEXT_LEAF,
+ name: "item1",
+ children: [ ]
+ }
+ ]
+ },
+ { // li@role="listitem"
+ role: ROLE_LISTITEM,
+ children: [
+ { // text leaf
+ role: ROLE_TEXT_LEAF,
+ name: "item2",
+ children: [ ]
+ }
+ ]
+ }
+ ]
+ };
+
+ testAccessibleTree("crazy_list", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Build the context dependent tree"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=804461">
+ Mozilla Bug 804461
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="list" role="list">
+ <div role="listitem">item1</div>
+ </div>
+
+ <div id="crazy_list" role="list">
+ <ul role="presentation">
+ <li>item1</li>
+ <li role="listitem">item2</li>
+ </ul>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_aria_menu.html b/accessible/tests/mochitest/tree/test_aria_menu.html
new file mode 100644
index 000000000..aa95b652b
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_aria_menu.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test accessible tree when ARIA role menuitem is used</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ // Menuitem with no popup.
+ tree =
+ { SECTION: [ // container
+ { MENUPOPUP: [ // menu
+ { MENUITEM: [
+ { STATICTEXT: [] }, // bullet
+ { TEXT_LEAF: [] }
+ ] }
+ ] }
+ ] }
+ testAccessibleTree("menu", tree);
+
+ // Menuitem with explicit no popup.
+ tree =
+ { SECTION: [ // container
+ { MENUPOPUP: [ // menu
+ { MENUITEM: [
+ { STATICTEXT: [] }, // bullet
+ { TEXT_LEAF: [] }
+ ] }
+ ] }
+ ] }
+ testAccessibleTree("menu_nopopup", tree);
+
+ // Menuitem with popup.
+ tree =
+ { SECTION: [ // container
+ { MENUPOPUP: [ // menu
+ { PARENT_MENUITEM: [ // menuitem with aria-haspopup="true"
+ { STATICTEXT: [] }, // bullet
+ { TEXT_LEAF: [] }
+ ] }
+ ] }
+ ] }
+ testAccessibleTree("menu_popup", tree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=786566"
+ title="ARIA menuitem acting as submenu should have PARENT_MENUITEM role">
+ Mozilla Bug 786566
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="menu">
+ <ul role="menu">
+ <li role="menuitem">Normal Menu</li>
+ </ul>
+ </div>
+
+ <div id="menu_nopopup">
+ <ul role="menu">
+ <li role="menuitem" aria-haspopup="false">Menu with explicit no popup</li>
+ </ul>
+ </div>
+
+ <div id="menu_popup">
+ <ul role="menu">
+ <li role="menuitem" aria-haspopup="true">Menu with popup</li>
+ </ul>
+ </div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_aria_owns.html b/accessible/tests/mochitest/tree/test_aria_owns.html
new file mode 100644
index 000000000..da4f52064
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_aria_owns.html
@@ -0,0 +1,187 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>@aria-owns attribute testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ ////////////////////////////////////////////////////////////////////////////
+ // Tests
+ ////////////////////////////////////////////////////////////////////////////
+
+ //enableLogging("tree,verbose"); // debug stuff
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ var tree =
+ { SECTION: [ // t1_1
+ { HEADING: [ // t1_2
+ // no kids, no loop
+ ] }
+ ] };
+ testAccessibleTree("t1_1", tree);
+
+ tree =
+ { SECTION: [ // t2_1
+ { GROUPING: [ // t2_2
+ { HEADING: [ // t2_3
+ // no kids, no loop
+ ] }
+ ] }
+ ] };
+ testAccessibleTree("t2_1", tree);
+
+ tree =
+ { SECTION: [ // t3_3
+ { GROUPING: [ // t3_1
+ { NOTE: [ // t3_2
+ { HEADING: [ // DOM child of t3_2
+ // no kids, no loop
+ ] }
+ ] }
+ ] }
+ ] };
+ testAccessibleTree("t3_3", tree);
+
+ tree =
+ { SECTION: [ // t4_1
+ { GROUPING: [ // DOM child of t4_1, aria-owns ignored
+ // no kids, no loop
+ ] }
+ ] };
+ testAccessibleTree("t4_1", tree);
+
+ tree =
+ { SECTION: [ // t5_1
+ { GROUPING: [ // DOM child of t5_1
+ { NOTE: [ // t5_2
+ { HEADING: [ // DOM child of t5_2
+ { FORM: [ // t5_3
+ { TOOLTIP: [ // DOM child of t5_3
+ // no kids, no loop
+ ]}
+ ]}
+ ]}
+ ] }
+ ] }
+ ] };
+ testAccessibleTree("t5_1", tree);
+
+ tree =
+ { SECTION: [ // t6_1
+ { RADIOBUTTON: [ ] },
+ { CHECKBUTTON: [ ] }, // t6_3, rearranged by aria-owns
+ { PUSHBUTTON: [ ] }, // t6_2, rearranged by aria-owns
+ ] };
+ testAccessibleTree("t6_1", tree);
+
+ tree =
+ { SECTION: [ // ariaowns_container
+ { SECTION: [ // ariaowns_self
+ { SECTION: [ // ariaowns_uncle
+ ] }
+ ] }
+ ] };
+ testAccessibleTree("ariaowns_container", tree);
+
+ tree =
+ { TABLE: [
+ { ROW: [
+ { GRID_CELL: [
+ { TEXT_LEAF: [] }
+ ] },
+ { GRID_CELL: [
+ { TEXT_LEAF: [] }
+ ] }
+ ] },
+ { ROW: [
+ { GRID_CELL: [
+ { TEXT_LEAF: [] }
+ ] },
+ { GRID_CELL: [
+ { TEXT_LEAF: [] }
+ ] }
+ ] }
+ ] };
+ testAccessibleTree("grid", tree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+
+ </script>
+</head>
+
+<body>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- simple loop -->
+ <div id="t1_1" aria-owns="t1_2"></div>
+ <div id="t1_2" aria-owns="t1_1" role="heading"></div>
+
+ <!-- loop -->
+ <div id="t2_2" aria-owns="t2_3" role="group"></div>
+ <div id="t2_1" aria-owns="t2_2"></div>
+ <div id="t2_3" aria-owns="t2_1" role="heading"></div>
+
+ <!-- loop #2 -->
+ <div id="t3_1" aria-owns="t3_2" role="group"></div>
+ <div id="t3_2" role="note">
+ <div aria-owns="t3_3" role="heading"></div>
+ </div>
+ <div id="t3_3" aria-owns="t3_1"></div>
+
+ <!-- self loop -->
+ <div id="t4_1"><div aria-owns="t4_1" role="group"></div></div>
+
+ <!-- natural and aria-owns hierarchy -->
+ <div id="t5_2" role="note"><div aria-owns="t5_3" role="heading"></div></div>
+ <div id="t5_1"><div aria-owns="t5_2" role="group"></div></div>
+ <div id="t5_3" role="form"><div aria-owns="t5_1" role="tooltip"></div></div>
+
+ <!-- rearrange children -->
+ <div id="t6_1" aria-owns="t6_3 t6_2">
+ <div id="t6_2" role="button"></div>
+ <div id="t6_3" role="checkbox"></div>
+ <div role="radio"></div>
+ </div>
+
+ <div id="ariaowns_container">
+ <div id="ariaowns_self"
+ aria-owns="aria_ownscontainer ariaowns_self ariaowns_uncle"></div>
+ </div>
+ <div id="ariaowns_uncle"></div>
+
+ <!-- grid -->
+ <div aria-owns="grid-row2" role="grid" id="grid">
+ <div role="row">
+ <div role="gridcell">cell 1,1</div>
+ <div role="gridcell">cell 1,2</div>
+ </div>
+ </div>
+ <div role="row" id="grid-row2">
+ <div role="gridcell">cell 2,1</div>
+ <div role="gridcell">cell 2,2</div>
+ </div>
+</body>
+
+</html>
diff --git a/accessible/tests/mochitest/tree/test_aria_presentation.html b/accessible/tests/mochitest/tree/test_aria_presentation.html
new file mode 100644
index 000000000..d8133ee9e
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_aria_presentation.html
@@ -0,0 +1,179 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test accessible tree when ARIA role presentation is used</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ // Presentation role don't allow accessible.
+ var tree =
+ { SECTION: [ // container
+ { TEXT_LEAF: [ ] }, // child text of 'presentation' node
+ { TEXT_LEAF: [ ] } // child text of 'none' node
+ ] };
+ testAccessibleTree("div_cnt", tree);
+
+ // Focusable element, 'presentation' and 'none' roles are ignored.
+ tree =
+ { SECTION: [ // container
+ { PUSHBUTTON: [ // button having 'presentation' role
+ { TEXT_LEAF: [ ] }
+ ] },
+ { PUSHBUTTON: [ // button having 'none' role
+ { TEXT_LEAF: [ ] }
+ ] }
+ ] };
+ testAccessibleTree("btn_cnt", tree);
+
+ // Presentation table, no table structure is exposed.
+ tree =
+ { SECTION: [ // container
+ { TEXT_CONTAINER: [ // td generic accessible inside 'presentation' table
+ { TEXT_LEAF: [ ] } // cell text
+ ] },
+ { TEXT_CONTAINER: [ // td generic accessible inside 'none' table
+ { TEXT_LEAF: [ ] } // cell text
+ ] }
+ ] };
+ testAccessibleTree("tbl_cnt", tree);
+
+ // Focusable table, 'presentation' and 'none' roles are ignored.
+ tree =
+ { SECTION: [ // container
+ { TABLE: [ // table having 'presentation' role
+ { ROW: [ // tr
+ { CELL: [ // td
+ { TEXT_LEAF: [ ] }
+ ] }
+ ] }
+ ] },
+ { TABLE: [ // table having 'none' role
+ { ROW: [ // tr
+ { CELL: [ // td
+ { TEXT_LEAF: [ ] }
+ ] }
+ ] }
+ ] }
+ ] };
+ testAccessibleTree("tblfocusable_cnt", tree);
+
+ // Presentation list, expose generic accesisble for list items.
+ tree =
+ { SECTION: [ // container
+ { TEXT_CONTAINER: [ // li generic accessible inside 'presentation' role
+ { TEXT_LEAF: [ ] } // li text
+ ] },
+ { TEXT_CONTAINER: [ // li generic accessible inside 'none' role
+ { TEXT_LEAF: [ ] } // li text
+ ] }
+ ] };
+ testAccessibleTree("list_cnt", tree);
+
+ // Has ARIA globals or referred by ARIA relationship, role='presentation'
+ // and role='none' are ignored.
+ tree =
+ { SECTION: [ // container
+ { LABEL: [ // label, has aria-owns
+ { TEXT_LEAF: [ ] },
+ { LABEL: [ // label, referenced by aria-owns
+ { TEXT_LEAF: [ ] }
+ ] },
+ ] },
+ { LABEL: [ // label, has aria-owns
+ { TEXT_LEAF: [ ] },
+ { LABEL: [ // label, referenced by aria-owns
+ { TEXT_LEAF: [ ] }
+ ] }
+ ] }
+ ] };
+ testAccessibleTree("airaglobalprop_cnt", tree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=548291"
+ title="Accessible tree of ARIA image maps">
+ Bug 548291
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=666504"
+ title="Ignore role presentation on focusable elements">
+ Bug 666504
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=971212"
+ title="Implement ARIA role=none">
+ Bug 971212
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="div_cnt"><div role="presentation">t</div><div role="none">t</div></div>
+
+ <div id="btn_cnt"><button role="presentation">btn</button><button role="none">btn</button></div>
+
+ <div id="tbl_cnt">
+ <table role="presentation">
+ <tr>
+ <td>cell</td>
+ </tr>
+ </table>
+ <table role="none">
+ <tr>
+ <td>cell</td>
+ </tr>
+ </table>
+ </div>
+
+ <div id="tblfocusable_cnt">
+ <table role="presentation" tabindex="0">
+ <tr>
+ <td>cell</td>
+ </tr>
+ </table>
+ <table role="none" tabindex="0">
+ <tr>
+ <td>cell</td>
+ </tr>
+ </table>
+ </div>
+
+ <div id="list_cnt">
+ <ul role="presentation">
+ <li>item</li>
+ </ul>
+ <ul role="none">
+ <li>item</li>
+ </ul>
+ </div>
+
+ <div id="airaglobalprop_cnt"><label
+ role="presentation" aria-owns="ariaowned">has aria-owns</label><label
+ role="presentation" id="ariaowned">referred by aria-owns</label><label
+ role="none" aria-owns="ariaowned2">has aria-owns</label><label
+ role="none" id="ariaowned2">referred by aria-owns</label></div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_aria_table.html b/accessible/tests/mochitest/tree/test_aria_table.html
new file mode 100644
index 000000000..64d3bd891
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_aria_table.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>ARIA table tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // table having rowgroups
+
+ var accTree =
+ { TABLE: [
+ { GROUPING: [
+ { ROW: [
+ { CELL: [
+ { TEXT_LEAF: [ ] }
+ ] }
+ ] }
+ ] },
+ ] };
+
+ testAccessibleTree("table", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="support ARIA table and cell roles"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1173364">
+ Bug 1173364
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="table" role="table">
+ <div role="rowgroup">
+ <div role="row">
+ <div role="cell">cell</div>
+ </div>
+ </div>
+ </div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_brokencontext.html b/accessible/tests/mochitest/tree/test_brokencontext.html
new file mode 100644
index 000000000..d63b4ae11
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_brokencontext.html
@@ -0,0 +1,265 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Broken context hierarchy</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ /**
+ * Return true if TD element has a generic accessible.
+ */
+ function isTDGeneric(aID)
+ {
+ return isAccessible(aID) && !isAccessible(aID, nsIAccessibleTableCell);
+ }
+
+ function checkIfNotAccessible(aID)
+ {
+ ok(!isAccessible(aID), "'" + aID + "' shouldn't be accessible");
+ }
+ function checkIfTDGeneric(aID)
+ {
+ ok(isTDGeneric(aID), "'" + aID + "' shouldn't have cell accessible");
+ }
+
+ function doTest()
+ {
+ ////////////////////////////////////////////////////////////////////////////
+ // HTML table elements outside table context.
+
+ // HTML table role="presentation"
+ checkIfNotAccessible("tr_in_presentation_table");
+ checkIfTDGeneric("th_in_presentation_table");
+ checkIfTDGeneric("td_in_presentation_table");
+
+ // HTML table role="button"
+ var tree =
+ { PUSHBUTTON: [ // table
+ { NOTHING: [ // tr
+ { NOTHING: [ // th
+ { TEXT_LEAF: [ ] }
+ ] },
+ { NOTHING: [ // td
+ { TEXT_LEAF: [ ] }
+ ] }
+ ] }
+ ] };
+ testAccessibleTree("button_table", tree);
+
+ // HTML table display:inline
+ checkIfNotAccessible("inline_table1");
+ checkIfNotAccessible("tr_in_inline_table1");
+ checkIfTDGeneric("td1_in_inline_table1");
+ checkIfTDGeneric("td2_in_inline_table1");
+
+ // HTML table display:inline inside table. We shouldn't be fooled
+ // by the outside table and shouldn't create table accessible and table cell
+ // accessible in this case.
+ checkIfNotAccessible("inline_table2");
+ checkIfNotAccessible("tr_in_inline_table2");
+ checkIfTDGeneric("td_in_inline_table2");
+
+ // HTML table display:block inside table.
+ checkIfNotAccessible("block_table");
+ checkIfNotAccessible("tr_in_block_table");
+ checkIfTDGeneric("td_in_block_table");
+
+ ////////////////////////////////////////////////////////////////////////////
+ // HTML list elements outside list context.
+
+ ok(!isAccessible("presentation_ul"),
+ "presentational ul shouldn't be accessible");
+ ok(isAccessible("item_in_presentation_ul"),
+ "li in presentational ul should have generic accessible");
+ ok(isAccessible("styleditem_in_presentation_ul"),
+ "list styled span in presentational ul should have generic accessible");
+
+ ok(!isAccessible("presentation_ol"),
+ "presentational ol shouldn't be accessible");
+ ok(isAccessible("item_in_presentation_ol"),
+ "li in presentational ol should have generic accessible");
+
+ ok(!isAccessible("presentation_dl"),
+ "presentational dl shouldn't be accessible");
+ ok(!isAccessible("dt_in_presentation_dl"),
+ "dt in presentational dl shouldn't be accessible");
+ ok(!isAccessible("dd_in_presentation_dl"),
+ "dd in presentational dl shouldn't be accessible");
+
+ tree =
+ { PUSHBUTTON: [ // ul
+ { NOTHING: [ // li
+ { STATICTEXT: [ ] },
+ { TEXT_LEAF: [ ] }
+ ] },
+ { NOTHING: [ // span styled as a list
+ { STATICTEXT: [ ] },
+ { TEXT_LEAF: [ ] }
+ ] }
+ ] };
+ testAccessibleTree("button_ul", tree);
+
+ tree =
+ { PUSHBUTTON: [ // ol
+ { NOTHING: [ // li
+ { STATICTEXT: [ ] },
+ { TEXT_LEAF: [ ] }
+ ] }
+ ] };
+ testAccessibleTree("button_ol", tree);
+
+ tree =
+ { PUSHBUTTON: [ // dl
+ { NOTHING: [ // dt
+ { TEXT_LEAF: [ ] }
+ ] },
+ { NOTHING: [ // dd
+ { TEXT_LEAF: [ ] }
+ ] }
+ ] };
+ testAccessibleTree("button_dl", tree);
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Styled as HTML table elements, accessible is created by tag name
+
+ tree =
+ { LINK: [ // a
+ { TEXT_LEAF: [ ] }
+ ] };
+ testAccessibleTree("a_as_td", tree);
+
+ tree =
+ { HEADING: [
+ { TEXT_LEAF: [ ] }
+ ] };
+ testAccessibleTree("h1_as_td", tree);
+ testAccessibleTree("h2_as_td", tree);
+ testAccessibleTree("h3_as_td", tree);
+ testAccessibleTree("h4_as_td", tree);
+ testAccessibleTree("h5_as_td", tree);
+ testAccessibleTree("h6_as_td", tree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=706849"
+ title="Create accessible by tag name as fallback if table descendant style is used out of table context">
+ Bug 706849
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=804461"
+ title="Build the context dependent tree ">
+ Bug 804461
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=945435"
+ title="Create generic accessible for td to not jamm the cell text">
+ Bug 945435
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- HTML table elements out of table -->
+ <table role="presentation">
+ <tr id="tr_in_presentation_table">
+ <th id="th_in_presentation_table">not a header</th>
+ <td id="td_in_presentation_table">not a cell</td>
+ </tr>
+ </table>
+
+ <table role="button" id="button_table">
+ <tr id="tr_in_button_table">
+ <th id="th_in_button_table">not a header</th>
+ <td id="td_in_button_table">not a cell</td>
+ </tr>
+ </table>
+
+ <table id="inline_table1" border="1" style="display:inline">
+ <tr id="tr_in_inline_table1">
+ <td id="td1_in_inline_table1">table1 cell1</td>
+ <td id="td2_in_inline_table1">table1 cell2</td>
+ </tr>
+ </table>
+
+ <table id="table_containing_inlinetable"><tr><td>
+ <table id="inline_table2" border="1" style="display:inline">
+ <tr id="tr_in_inline_table2">
+ <td id="td_in_inline_table2">cell</td>
+ </tr>
+ </table>
+ </td></tr></table>
+
+ <table>
+ <tr>
+ <td style="display:block">
+ <table id="block_table" style="display:inline">
+ <tr id="tr_in_block_table">
+ <td id="td_in_block_table">cell0</td>
+ </tr>
+ </table>
+ </td>
+ <td>cell1</td>
+ </tr>
+ </table>
+
+ <!-- HTML list elements out of list -->
+ <ul role="presentation" id="presentation_ul">
+ <li id="item_in_presentation_ul">item</li>
+ <span id="styleditem_in_presentation_ul"
+ style="display:list-item">Oranges</span>
+ </ul>
+
+ <ol role="presentation" id="presentation_ol">
+ <li id="item_in_presentation_ol">item</li>
+ </ol>
+
+ <dl role="presentation" id="presentation_dl">
+ <dt id="dt_in_presentation_dl">term</dt>
+ <dd id="dd_in_presentation_dl">definition</dd>
+ </dl>
+
+ <ul role="button" id="button_ul">
+ <li id="item_in_button_ul">item</li>
+ <span id="styleditem_in_button_ul"
+ style="display:list-item">Oranges</span>
+ </ul>
+
+ <ol role="button" id="button_ol">
+ <li id="item_in_button_ul">item</li>
+ </ol>
+
+ <dl role="button" id="button_dl">
+ <dt id="dt_in_button_dl">term</ld>
+ <dd id="dd_in_button_dl">definition</dd>
+ </dl>
+
+ <!-- styled as HTML table elements -->
+ <a id="a_as_td" style="display:table-cell;" href="http://www.google.com">Google</a>
+ <h1 id="h1_as_td" style="display: table-cell;">h1</h1>
+ <h2 id="h2_as_td" style="display: table-cell;">h2</h2>
+ <h3 id="h3_as_td" style="display: table-cell;">h3</h3>
+ <h4 id="h4_as_td" style="display: table-cell;">h4</h4>
+ <h5 id="h5_as_td" style="display: table-cell;">h5</h5>
+ <h6 id="h6_as_td" style="display: table-cell;">h6</h6>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_button.xul b/accessible/tests/mochitest/tree/test_button.xul
new file mode 100644
index 000000000..39d189f41
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_button.xul
@@ -0,0 +1,73 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL button hierarchy tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // button
+
+ var accTree = {
+ role: ROLE_PUSHBUTTON,
+ name: "hello",
+ children: [ ]
+ };
+ testAccessibleTree("button1", accTree);
+
+ //////////////////////////////////////////////////////////////////////////
+ // toolbarbutton
+
+ var accTree = {
+ role: ROLE_PUSHBUTTON,
+ name: "hello",
+ children: [ ]
+ };
+ testAccessibleTree("button2", accTree);
+
+ SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=249292"
+ title="Ensure accessible children for toolbarbutton types 'menu' and 'menu-button'">
+ Mozilla Bug 249292
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <button id="button1" label="hello"/>
+ <toolbarbutton id="button2" label="hello"/>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/tree/test_canvas.html b/accessible/tests/mochitest/tree/test_canvas.html
new file mode 100644
index 000000000..314ad84c7
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_canvas.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=495912
+-->
+<head>
+ <title>File Input Control tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ var accTree =
+ { CANVAS: [
+ { CHECKBUTTON: [] },
+ { ENTRY: [] }
+ ] };
+
+ testAccessibleTree("canvas", accTree);
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Expose alternative content in Canvas element to ATs"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=495912">Mozilla Bug 495912</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <canvas id="canvas" tabindex="0"><input type="checkbox"><input></canvas>
+
+ <script type="text/javascript">
+ var c=document.getElementById("canvas");
+ var cxt=c.getContext("2d");
+ cxt.fillStyle="#005500";
+ cxt.fillRect(0,0,150,75);
+ </script>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_combobox.xul b/accessible/tests/mochitest/tree/test_combobox.xul
new file mode 100644
index 000000000..79ce866dc
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_combobox.xul
@@ -0,0 +1,291 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL menulist and textbox @autocomplete hierarchy tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // menulist
+
+ var selectedOptionChildren = [];
+ if (MAC) {
+ // checkmark is part of the Mac menu styling
+ selectedOptionChildren = [{
+ role: ROLE_STATICTEXT,
+ children: []
+ }];
+ }
+
+ var accTree = {
+ role: ROLE_COMBOBOX,
+ children: [
+ {
+ role: ROLE_COMBOBOX_LIST,
+ children: [
+ {
+ role: ROLE_COMBOBOX_OPTION,
+ children: selectedOptionChildren
+ },
+ {
+ role: ROLE_COMBOBOX_OPTION,
+ children: []
+ }
+ ]
+ }
+ ]
+ };
+
+ testAccessibleTree("menulist", accTree);
+
+ //////////////////////////////////////////////////////////////////////////
+ // editable menulist
+
+ accTree = {
+ role: ROLE_COMBOBOX,
+ children: [
+ {
+ role: ROLE_ENTRY,
+ children: [
+ // no text leaf accessible for text node
+ ]
+ },
+ {
+ role: ROLE_COMBOBOX_LIST, // context menu
+ children: []
+ },
+ {
+ role: ROLE_PUSHBUTTON, // dropmarker
+ children: []
+ },
+ {
+ role: ROLE_COMBOBOX_LIST, // option list
+ children: [
+ {
+ role: ROLE_COMBOBOX_OPTION,
+ children: []
+ },
+ {
+ role: ROLE_COMBOBOX_OPTION,
+ children: []
+ }
+ ]
+ }
+ ]
+ };
+
+ if (!MAC) {
+ testAccessibleTree("menulist2", accTree);
+ } else {
+ todo(false, "Make this test pass on OSX (bug 551957)");
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // textbox@type=autocomplete #1 (history)
+
+ accTree = {
+ // textbox
+ role: ROLE_AUTOCOMPLETE,
+ children: [
+ {
+ // html:input
+ role: ROLE_ENTRY,
+ children: [
+ {
+ // #text
+ role: ROLE_TEXT_LEAF,
+ name: "http://mochi.test:8888/redirect-a11y.html",
+ children: []
+ }
+ ]
+ },
+ {
+ // xul:menupopup
+ role: ROLE_COMBOBOX_LIST, // context menu popup
+ children: []
+ }
+ ]
+ };
+
+ // XPFE and Toolkit autocomplete widgets differ.
+ var ac1h = document.getElementById("autocomplete");
+ if ("clearResults" in ac1h) {
+ SimpleTest.ok(true, "Testing (Old) XPFE autocomplete widget. (ac1h)");
+
+ // Popup is always created.
+ accTree.children.push(
+ {
+ // xul:panel
+ role: ROLE_COMBOBOX_LIST,
+ children: [
+ {
+ // xul:tree
+ role: ROLE_TABLE,
+ children: [
+ {
+ // xul:treecols
+ role: ROLE_LIST,
+ children: [
+ {
+ // xul:treecol
+ role: ROLE_COLUMNHEADER,
+ children: []
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ );
+ } else {
+ SimpleTest.ok(true, "Testing (New) Toolkit autocomplete widget. (ac1h)");
+
+ // Popup is lazily created, so not present in this case.
+ }
+
+ testAccessibleTree("autocomplete", accTree);
+
+ //////////////////////////////////////////////////////////////////////////
+ // textbox@type=autocomplete #2 (child menupoup)
+
+ accTree = {
+ // textbox
+ role: ROLE_AUTOCOMPLETE,
+ children: [
+ {
+ // menupopup
+ role: ROLE_COMBOBOX_LIST, // autocomplete menu popup
+ children: [
+ {
+ // menuitem
+ role: ROLE_COMBOBOX_OPTION,
+ children: []
+ }
+ ]
+ },
+ {
+ // html:input
+ role: ROLE_ENTRY,
+ children: [
+ // no text leaf accessible for text node
+ ]
+ },
+ {
+ // xul:menupopup
+ role: ROLE_COMBOBOX_LIST, // context menu popup
+ children: []
+ }
+ ]
+ };
+
+ // XPFE and Toolkit autocomplete widgets differ.
+ var ac2cmp = document.getElementById("autocomplete2");
+ if ("clearResults" in ac2cmp) {
+ SimpleTest.ok(true, "Testing (Old) XPFE autocomplete widget. (ac2mp)");
+
+ // Popup is always created.
+ accTree.children.push(
+ {
+ // xul:panel
+ role: ROLE_COMBOBOX_LIST,
+ children: [
+ {
+ // xul:tree
+ role: ROLE_TABLE,
+ children: [
+ {
+ // xul:treecols
+ role: ROLE_LIST,
+ children: [
+ {
+ // xul:treecol
+ role: ROLE_COLUMNHEADER,
+ children: []
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ );
+ } else {
+ SimpleTest.ok(true, "Testing (New) Toolkit autocomplete widget. (ac2mp)");
+
+ // Popup is lazily created, so not present in this case.
+ }
+
+ testAccessibleTree("autocomplete2", accTree);
+
+ SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=249292"
+ title="Ensure accessible children for toolbarbutton types 'menu' and 'menu-button'">
+ Mozilla Bug 249292
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=626660"
+ title="Cache rendered text on a11y side">
+ Mozilla Bug 626660
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <menulist id="menulist">
+ <menupopup>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ </menupopup>
+ </menulist>
+
+ <menulist id="menulist2" editable="true">
+ <menupopup>
+ <menuitem label="item"/>
+ <menuitem label="item"/>
+ </menupopup>
+ </menulist>
+
+ <textbox id="autocomplete" type="autocomplete"
+ autocompletesearch="unifiedcomplete"
+ value="http://mochi.test:8888/redirect-a11y.html"/>
+
+ <textbox id="autocomplete2" type="autocomplete">
+ <menupopup>
+ <menuitem label="item1"/>
+ </menupopup>
+ </textbox>
+ </vbox>
+ </hbox>
+
+</window>
diff --git a/accessible/tests/mochitest/tree/test_cssflexbox.html b/accessible/tests/mochitest/tree/test_cssflexbox.html
new file mode 100644
index 000000000..f3786ac92
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_cssflexbox.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>CSS flexbox tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ // Ensure that flexbox ordering and absolute positioning do not affect
+ // the accessibility tree.
+ // Note that there is no accessible for a div with display:flex style.
+ var accTree = {
+ role: ROLE_SECTION,
+ children: [
+ { // Bug 1277559. Button outside the flexed content
+ role: ROLE_PUSHBUTTON,
+ name: "Button"
+ },
+ { // Visually first button in the 3 button row
+ role: ROLE_PUSHBUTTON,
+ name: "First"
+ },
+ { // Flushed right third button in the 3 button row
+ role: ROLE_PUSHBUTTON,
+ name: "Second"
+ },
+ { // Middle button in the 3 button row
+ role: ROLE_PUSHBUTTON,
+ name: "Third"
+ }, // end bug 1277559
+ { // Bug 962558: DOM first, Order 2.
+ role: ROLE_PUSHBUTTON,
+ name: "two, tab first"
+ },
+ { // DOM order second, flex order 1
+ role: ROLE_PUSHBUTTON,
+ name: "one, tab second"
+ } // end bug 962558
+ ]
+ };
+ testAccessibleTree("flex_elements", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="flex_elements">
+ <button type="button">Button</button>
+ <div style="position: relative; display: flex; width: 200px;">
+ <button type="button" style="order: 1">First</button>
+ <button type="button" style="order: 2; position: absolute; right: 0">Second</button>
+ <button type="button" style="order: 3">Third</button>
+ </div>
+ <div style="display: flex">
+ <button id="two" style="order: 2">two, tab first</button>
+ <button id="one" style="order: 1">one, tab second</button>
+ </div>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_cssoverflow.html b/accessible/tests/mochitest/tree/test_cssoverflow.html
new file mode 100644
index 000000000..c67128fbc
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_cssoverflow.html
@@ -0,0 +1,146 @@
+<html>
+
+<head>
+ <title>CSS overflow testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <style>
+ a.link:focus {
+ overflow: scroll;
+ }
+ </style>
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function focusAnchor(aID)
+ {
+ this.linkNode = getNode(aID);
+ this.link = getAccessible(this.linkNode);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, getAccessible, this.linkNode)
+ ];
+
+ this.invoke = function focusAnchor_invoke()
+ {
+ this.linkNode.focus();
+ }
+
+ this.check = function focusAnchor_check(aEvent)
+ {
+ todo_is(this.link, aEvent.accessible,
+ "Focus should be fired against new link accessible!");
+ }
+
+ this.getID = function focusAnchor_getID()
+ {
+ return "focus a:focus{overflow:scroll} #1";
+ }
+ }
+
+ function tabAnchor(aID)
+ {
+ this.linkNode = getNode(aID);
+ this.link = getAccessible(this.linkNode);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, getAccessible, this.linkNode)
+ ];
+
+ this.invoke = function tabAnchor_invoke()
+ {
+ synthesizeKey("VK_TAB", { shiftKey: false });
+ }
+
+ this.check = function tabAnchor_check(aEvent)
+ {
+ todo_is(this.link, aEvent.accessible,
+ "Focus should be fired against new link accessible!");
+ }
+
+ this.getID = function tabAnchor_getID()
+ {
+ return "focus a:focus{overflow:scroll} #2";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Do tests
+
+ var gQueue = null;
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ function doTests()
+ {
+ // Shift+Tab not working, and a test timeout, bug 746977
+ if (MAC) {
+ todo(false, "Shift+tab isn't working on OS X, needs to be disabled until bug 746977 is fixed!");
+ SimpleTest.finish();
+ return;
+ }
+
+ gQueue = new eventQueue();
+
+ // CSS 'overflow: scroll' property setting and unsetting causes accessible
+ // recreation (and fire show/hide events). For example, the focus and
+ // blur of HTML:a with ':focus {overflow: scroll; }' CSS style causes its
+ // accessible recreation. The focus event should be fired on new
+ // accessible.
+ gQueue.push(new focusAnchor("a"));
+ gQueue.push(new tabAnchor("a2"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=591163"
+ title="mochitest for bug 413777: focus the a:focus {overflow: scroll;} shouldn't recreate HTML a accessible">
+ Mozilla Bug 591163
+ </a><br>
+ <a target="_blank"
+ title="Rework accessible tree update code"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275">
+ Mozilla Bug 570275
+ </a><br>
+ <a target="_blank"
+ title="Text control frames should accept dynamic changes to the CSS overflow property"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=686247">
+ Mozilla Bug 686247
+ </a><br>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <div id="eventdump"></div>
+
+ <div>
+ <a id="a" class="link" href="www">link</a>
+ </div>
+ <div>
+ <a id="a2" class="link" href="www">link2</a>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_dochierarchy.html b/accessible/tests/mochitest/tree/test_dochierarchy.html
new file mode 100644
index 000000000..0104c6aba
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_dochierarchy.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test document hierarchy</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ // tabDoc and testDoc are different documents depending on whether test
+ // is running in standalone mode or not.
+
+ var root = getRootAccessible();
+ var tabDoc = window.parent ?
+ getAccessible(window.parent.document, [nsIAccessibleDocument]) :
+ getAccessible(document, [nsIAccessibleDocument]);
+ var testDoc = getAccessible(document, [nsIAccessibleDocument]);
+ var iframeDoc = getAccessible("iframe").firstChild.
+ QueryInterface(nsIAccessibleDocument);
+
+ is(root.parentDocument, null,
+ "Wrong parent document of root accessible");
+ ok(root.childDocumentCount >= 1,
+ "Wrong child document count of root accessible");
+
+ var tabDocumentFound = false;
+ for (var i = 0; i < root.childDocumentCount && !tabDocumentFound; i++) {
+ tabDocumentFound = root.getChildDocumentAt(i) == tabDoc;
+ }
+ ok(tabDocumentFound,
+ "Tab document not found in children of the root accessible");
+
+ is(tabDoc.parentDocument, root,
+ "Wrong parent document of tab document");
+ is(tabDoc.childDocumentCount, 1,
+ "Wrong child document count of tab document");
+ is(tabDoc.getChildDocumentAt(0), (tabDoc == testDoc ? iframeDoc : testDoc),
+ "Wrong child document at index 0 of tab document");
+
+ if (tabDoc != testDoc) {
+ is(testDoc.parentDocument, tabDoc,
+ "Wrong parent document of test document");
+ is(testDoc.childDocumentCount, 1,
+ "Wrong child document count of test document");
+ is(testDoc.getChildDocumentAt(0), iframeDoc,
+ "Wrong child document at index 0 of test document");
+ }
+
+ is(iframeDoc.parentDocument, (tabDoc == testDoc ? tabDoc : testDoc),
+ "Wrong parent document of iframe document");
+ is(iframeDoc.childDocumentCount, 0,
+ "Wrong child document count of iframe document");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=592913"
+ title="Provide a way to quickly determine whether an accessible object is a descendant of a tab document">
+ Mozilla Bug 592913
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <iframe src="about:mozilla" id="iframe"></iframe>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_dockids.html b/accessible/tests/mochitest/tree/test_dockids.html
new file mode 100644
index 000000000..943ac18b3
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_dockids.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test document hierarchy</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ //gA11yEventDumpToConsole = true;
+ //enableLogging("tree,verbose");
+ function doTest()
+ {
+ var tree =
+ { DOCUMENT: [
+ { TEXT_CONTAINER: [ // head
+ { TEXT_CONTAINER: [ // link
+ { STATICTEXT: [] }, // generated content
+ { STATICTEXT: [] } // generated content
+ ] }
+ ] },
+ { TEXT_LEAF: [ ] }, // body text
+ { ENTRY: [ ] }, // input under document element
+ { TEXT_CONTAINER: [ // link under document element
+ { TEXT_LEAF: [ ] }, // link content
+ { STATICTEXT: [ ] }, // generated content
+ { STATICTEXT: [ ] } // generated content
+ ] },
+ { LINK: [ // anchor under document element
+ { TEXT_LEAF: [ ] } // anchor content
+ ] },
+ ] };
+ testAccessibleTree(getNode("iframe").contentDocument, tree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=608887"
+ title="Elements appended outside the body aren't accessible">
+ Mozilla Bug 608887
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <iframe src="dockids.html" id="iframe"></iframe>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_filectrl.html b/accessible/tests/mochitest/tree/test_filectrl.html
new file mode 100644
index 000000000..f989d1eea
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_filectrl.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=483573
+-->
+<head>
+ <title>File Input Control tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ var accTree = {
+ role: ROLE_TEXT_CONTAINER,
+ children: [
+ {
+ role: ROLE_PUSHBUTTON
+ },
+ {
+ role: ROLE_LABEL,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ }
+ ],
+ },
+ ]
+ };
+ testAccessibleTree("filectrl", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Expose HTML5 video and audio elements' embedded controls through accessibility APIs"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=483573">Mozilla Bug 483573</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input type="file" id="filectrl" />
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_formctrl.html b/accessible/tests/mochitest/tree/test_formctrl.html
new file mode 100644
index 000000000..c5d5bc0b1
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_formctrl.html
@@ -0,0 +1,132 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>HTML form controls tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ // input@type="checkbox"
+ var accTree = {
+ role: ROLE_CHECKBUTTON,
+ children: [ ]
+ };
+
+ testAccessibleTree("checkbox", accTree);
+
+ // input@type="radio"
+ accTree = {
+ role: ROLE_RADIOBUTTON,
+ children: [ ]
+ };
+
+ testAccessibleTree("radio", accTree);
+
+ // input@type="button" and input@type="submit"
+ // button
+ accTree = {
+ role: ROLE_PUSHBUTTON,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF // XXX Bug 567203
+ }
+ ]
+ };
+
+ testAccessibleTree("btn1", accTree);
+ testAccessibleTree("submit", accTree);
+ testAccessibleTree("btn2", accTree);
+
+ // input@type="image"
+ accTree = {
+ role: ROLE_PUSHBUTTON,
+ children: [
+ {
+ role: ROLE_STATICTEXT
+ }
+ ]
+ };
+ testAccessibleTree("image_submit", accTree);
+
+ // input@type="range"
+ accTree = { SLIDER: [ ] };
+ testAccessibleTree("range", accTree);
+
+ // input@type="number"
+ accTree =
+ { SPINBUTTON: [
+ { ENTRY: [ ] },
+ { PUSHBUTTON: [ ] },
+ { PUSHBUTTON: [ ] }
+ ] };
+ testAccessibleTree("number", accTree);
+
+ // output
+ accTree = {
+ role: ROLE_SECTION,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF
+ }
+ ]
+ };
+ testAccessibleTree("output", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Fix O(n^2) access to all the children of a container"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=342045">
+ Bug 342045
+ </a>
+ <a target="_blank"
+ title="add test for role of input type='image'"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=524521">
+ Bug 524521
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=558036"
+ title="make HTML <output> accessible">
+ Bug 558036
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=559764"
+ title="make HTML5 input@type=range element accessible">
+ Bug 559764
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <input type="checkbox" id="checkbox">
+ <input type="radio" id="radio">
+ <input type="button" value="button" id="btn1">
+ <button id="btn2">button</button>
+
+ <input type="submit" id="submit">
+ <input type="image" id="image_submit">
+ <input type="range" id="range">
+ <input type="number" id="number">
+ <output id="output">1337</output>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_formctrl.xul b/accessible/tests/mochitest/tree/test_formctrl.xul
new file mode 100644
index 000000000..34dc3795e
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_formctrl.xul
@@ -0,0 +1,130 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<!-- Firefox toolbar -->
+<?xml-stylesheet href="chrome://browser/content/browser.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL checkbox and radio hierarchy tests">
+
+ <!-- Firefox toolbar -->
+ <script type="application/javascript"
+ src="chrome://browser/content/browser.js"/>
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function doTest()
+ {
+ // checkbox
+ var accTree = {
+ role: ROLE_CHECKBUTTON,
+ children: [ ]
+ };
+
+ testAccessibleTree("checkbox", accTree);
+
+ // radiogroup
+ accTree = {
+ role: ROLE_RADIO_GROUP,
+ children: [
+ {
+ role: ROLE_RADIOBUTTON,
+ children: [ ]
+ },
+ {
+ role: ROLE_RADIOBUTTON,
+ children: [ ]
+ }
+ ]
+ };
+
+ testAccessibleTree("radiogroup", accTree);
+
+ // toolbar
+ accTree = {
+ role: ROLE_TOOLBAR,
+ name: "My toolbar",
+ children: [
+ {
+ role: ROLE_PUSHBUTTON,
+ name: "hello",
+ children: [ ]
+ }
+ ]
+ };
+
+ testAccessibleTree("toolbar", accTree);
+
+ // toolbar
+ accTree = {
+ role: ROLE_TOOLBAR,
+ name: "My second toolbar",
+ children: [
+ {
+ role: ROLE_PUSHBUTTON,
+ name: "hello",
+ children: [ ]
+ }
+ ]
+ };
+
+ testAccessibleTree("toolbar2", accTree);
+
+ if (!SEAMONKEY)
+ testAccessibleTree("tb_customizable", { TOOLBAR: [] });
+
+ SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=342045"
+ title="Fix O(n^2) access to all the children of a container">
+ Mozilla Bug 342045
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <checkbox id="checkbox" label="checkbox"/>
+ <radiogroup id="radiogroup">
+ <radio label="radio1"/>
+ <radio label="radio2"/>
+ </radiogroup>
+ <toolbar id="toolbar" toolbarname="My toolbar">
+ <toolbarbutton id="button1" label="hello"/>
+ </toolbar>
+ <toolbar id="toolbar2" toolbarname="2nd" aria-label="My second toolbar">
+ <toolbarbutton id="button2" label="hello"/>
+ </toolbar>
+
+ <toolbar id="tb_customizable" customizable="true"/>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/tree/test_gencontent.html b/accessible/tests/mochitest/tree/test_gencontent.html
new file mode 100644
index 000000000..0932f5c29
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_gencontent.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Generated content tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <style>
+ .gentext:before {
+ content: "START"
+ }
+ .gentext:after {
+ content: "END"
+ }
+ </style>
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ // :before and :after pseudo styles
+ var accTree = {
+ role: ROLE_SECTION,
+ children: [
+ {
+ role: ROLE_STATICTEXT,
+ name: "START"
+ },
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "MIDDLE"
+ },
+ {
+ role: ROLE_STATICTEXT,
+ name: "END"
+ }
+ ]
+ };
+
+ testAccessibleTree("gentext", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Clean up our tree walker"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=530081">
+ Mozilla Bug 530081
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div class="gentext" id="gentext">MIDDLE</div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_groupbox.xul b/accessible/tests/mochitest/tree/test_groupbox.xul
new file mode 100644
index 000000000..0b68e6018
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_groupbox.xul
@@ -0,0 +1,64 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL groupbox hierarchy tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function doTest()
+ {
+ var accTree =
+ { GROUPING: [
+ { LABEL: [
+ { TEXT_LEAF: [ ] }
+ ] },
+ { CHECKBUTTON: [ ] }
+ ] };
+ testAccessibleTree("groupbox", accTree);
+
+ SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=342045"
+ title="Fix O(n^2) access to all the children of a container">
+ Mozilla Bug 342045
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <groupbox id="groupbox">
+ <caption label="Some caption" />
+ <checkbox label="some checkbox label" />
+ </groupbox>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/tree/test_iframe.html b/accessible/tests/mochitest/tree/test_iframe.html
new file mode 100644
index 000000000..dae398fd8
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_iframe.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Outer document accessible tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ var accTree = {
+ role: ROLE_INTERNAL_FRAME,
+ children: [
+ {
+ role: ROLE_DOCUMENT
+ }
+ ]
+ };
+
+ testAccessibleTree("iframe", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Fix O(n^2) access to all the children of a container"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=342045">
+ Mozilla Bug 342045
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <iframe id="iframe" src="about:mozilla">
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_img.html b/accessible/tests/mochitest/tree/test_img.html
new file mode 100644
index 000000000..9ac52da39
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_img.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML img tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ //gA11yEventDumpToConsole = true;
+ function doPreTest()
+ {
+ waitForImageMap("imgmap", doTest);
+ }
+
+ function doTest()
+ {
+ // image map
+ var accTree = {
+ role: ROLE_IMAGE_MAP,
+ children: [
+ {
+ role: ROLE_LINK,
+ children: []
+ },
+ {
+ role: ROLE_LINK,
+ children: []
+ }
+ ]
+ };
+
+ testAccessibleTree("imgmap", accTree);
+
+ // img
+ accTree = {
+ role: ROLE_GRAPHIC,
+ children: []
+ };
+
+ testAccessibleTree("img", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doPreTest);
+ </script>
+
+</head>
+<body>
+
+ <a target="_blank"
+ title="Fix O(n^2) access to all the children of a container"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=342045">
+ Mozilla Bug 342045
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <map name="atoz_map">
+ <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b"
+ coords="17,0,30,14" alt="b" shape="rect">
+ <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#a"
+ coords="0,0,13,14" alt="a" shape="rect">
+ </map>
+
+ <img id="imgmap" width="447" height="15"
+ usemap="#atoz_map"
+ src="../letters.gif">
+
+ <img id="img" src="../moz.png">
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_invalid_img.xhtml b/accessible/tests/mochitest/tree/test_invalid_img.xhtml
new file mode 100644
index 000000000..2079c86ee
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_invalid_img.xhtml
@@ -0,0 +1,50 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>invalid html img</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script>
+ <![CDATA[
+ function doTest()
+ {
+ document.getElementsByTagName("img")[0].firstChild.data = "2";
+
+ var accTree = {
+ role: ROLE_TEXT,
+ children: [ { role: ROLE_TEXT_LEAF } ]
+ };
+ testAccessibleTree("the_img", accTree);
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="use HyperTextAccessible for invalid img"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=852129">
+ Mozilla Bug 852129
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <img id="the_img">1</img>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_invalidationlist.html b/accessible/tests/mochitest/tree/test_invalidationlist.html
new file mode 100644
index 000000000..c42891a5d
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_invalidationlist.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test document hierarchy</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ var tree =
+ { SECTION: [
+ { SECTION: [ // div
+ { LABEL: [ ] } // link
+ ] },
+ { SECTION: [ // div table-cell referred by label
+ { TEXT_LEAF: [ ] }, // 'Z'
+ { TEXT_LEAF: [ ] } // ' '
+ ] }
+ ] };
+ testAccessibleTree("container", tree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=673757"
+ title="Do not process invalidation list while tree is created">
+ Mozilla Bug 673757
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="container">
+ <div><label for="x"></label></div>
+ <div style="display: table-cell;" id="x">Z<span style='white-space:pre'> </span><span></span></div>
+ </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_list.html b/accessible/tests/mochitest/tree/test_list.html
new file mode 100644
index 000000000..bf5e8768d
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_list.html
@@ -0,0 +1,247 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML ul/li element tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function listItemTree(aBulletText, aName, aSubtree)
+ {
+ var obj = {
+ role: ROLE_LISTITEM,
+ children: [
+ {
+ role: ROLE_STATICTEXT,
+ name: aBulletText
+ },
+ {
+ role: ROLE_TEXT_LEAF,
+ name: aName
+ }
+ ]
+ };
+
+ if (aSubtree)
+ obj.children.push(aSubtree);
+
+ return obj;
+ }
+
+ function doTest()
+ {
+ // list1
+ var discAccTree = {
+ role: ROLE_LIST,
+ children: [
+ new listItemTree(kDiscBulletText, "Oranges"),
+ new listItemTree(kDiscBulletText, "Apples"),
+ new listItemTree(kDiscBulletText, "Bananas")
+ ]
+ };
+
+ testAccessibleTree("list1", discAccTree);
+
+ // list2
+ var circleAccTree = {
+ role: ROLE_LIST,
+ children: [
+ new listItemTree(kCircleBulletText, "Oranges"),
+ new listItemTree(kCircleBulletText, "Apples"),
+ new listItemTree(kCircleBulletText, "Bananas")
+ ]
+ };
+
+ testAccessibleTree("list2", circleAccTree);
+
+ // list3
+ var squareAccTree = {
+ role: ROLE_LIST,
+ children: [
+ new listItemTree(kSquareBulletText, "Oranges"),
+ new listItemTree(kSquareBulletText, "Apples"),
+ new listItemTree(kSquareBulletText, "Bananas")
+ ]
+ };
+
+ testAccessibleTree("list3", squareAccTree);
+
+ // list4
+ var nestedAccTree = {
+ role: ROLE_LIST,
+ children: [
+ new listItemTree("1. ", "Oranges"),
+ new listItemTree("2. ", "Apples"),
+ new listItemTree("3. ", "Bananas", circleAccTree)
+ ]
+ };
+
+ testAccessibleTree("list4", nestedAccTree);
+
+ // dl list
+ var tree =
+ { DEFINITION_LIST: [ // dl
+ { TERM: [ // dt
+ { TEXT_LEAF: [] },
+ ] },
+ { DEFINITION: [ // dd
+ { TEXT_LEAF: [] }
+ ] },
+ { TERM: [ // dt
+ { TEXT_LEAF: [] }
+ ] },
+ { DEFINITION: [ // dd
+ { TEXT_LEAF: [] }
+ ] }
+ ] };
+
+ testAccessibleTree("list5", tree);
+
+ // dl list inside ordered list
+ tree =
+ { LIST: [ // ol
+ { LISTITEM: [ // li
+ { STATICTEXT: [ ] },
+ { DEFINITION_LIST: [ // dl
+ { TERM: [ // dt
+ { TEXT_LEAF: [] }
+ ] },
+ { DEFINITION: [ // dd
+ { TEXT_LEAF: [] }
+ ] }
+ ] }
+ ] }
+ ] };
+
+ testAccessibleTree("list6", tree);
+
+ // li having no display:list-item style
+ var tree =
+ { LIST: [ // ul
+ { LISTITEM: [ // li
+ { TEXT_LEAF: [] },
+ ] },
+ { TEXT_LEAF: [] },
+ { LISTITEM: [ // li
+ { TEXT_LEAF: [] }
+ ] }
+ ] };
+ testAccessibleTree("list7", tree);
+
+ var tree =
+ { LIST: [ // ul
+ { LISTITEM: [ // li
+ { TEXT_LEAF: [] },
+ ] },
+ { LISTITEM: [ // li
+ { TEXT_LEAF: [] }
+ ] }
+ ] };
+ testAccessibleTree("list8", tree);
+
+ // span having display:list-item style
+ testAccessibleTree("list9", discAccTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Fix O(n^2) access to all the children of a container"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=342045">
+ Mozilla Bug 342045
+ </a>
+ <a target="_blank"
+ title="Wrong accessible is created for HTML:li having block display style"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=507555">
+ Mozilla Bug 507555
+ </a>
+ <a target="_blank"
+ title="Bullets of nested not ordered lists have one and the same character."
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=604587">
+ Mozilla Bug 604587
+ </a>
+ <a target="_blank"
+ title="Fix list bullets for DL list (crash [@ nsBulletFrame::GetListItemText])"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=629114">
+ Mozilla Bug 629114
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <ul id="list1">
+ <li id="l1_li1">Oranges</li>
+ <li id="l1_li2">Apples</li>
+ <li id="l1_li3">Bananas</li>
+ </ul>
+
+ <ul id="list2" style="list-style-type: circle">
+ <li id="l2_li1">Oranges</li>
+ <li id="l2_li2">Apples</li>
+ <li id="l2_li3">Bananas</li>
+ </ul>
+
+ <ul id="list3" style="list-style-type: square">
+ <li id="l3_li1">Oranges</li>
+ <li id="l3_li2">Apples</li>
+ <li id="l3_li3">Bananas</li>
+ </ul>
+
+ <ol id="list4">
+ <li id="li4">Oranges</li>
+ <li id="li5">Apples</li>
+ <li id="li6">Bananas<ul>
+ <li id="n_li4">Oranges</li>
+ <li id="n_li5">Apples</li>
+ <li id="n_li6">Bananas</li>
+ </ul>
+ </li>
+ </ol>
+
+ <dl id="list5">
+ <dt>item1</dt><dd>description</dd>
+ <dt>item2</td><dd>description</dd>
+ </dl>
+
+ <ol id="list6">
+ <li>
+ <dl id="dl">
+ <dt>item1</dt><dd>description</dd>
+ </dl>
+ </li>
+ </ol>
+
+ <!-- display style different than list-item -->
+ <ul id="list7">
+ <li id="l7_li1" style="display:inline-block;">Oranges</li>
+ <li id="l7_li2" style="display:inline-block;">Apples</li>
+ </ul>
+
+ <ul id="list8">
+ <li id="l8_li1" style="display:inline; float:right;">Oranges</li>
+ <li id="l8_li2" style="display:inline; float:right;">Apples</li>
+ </ul>
+
+ <!-- list-item display style -->
+ <ul id="list9">
+ <span id="l9_li1" style="display:list-item">Oranges</span>
+ <span id="l9_li2" style="display:list-item">Apples</span>
+ <span id="l9_li3" style="display:list-item">Bananas</span>
+ </ul>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_map.html b/accessible/tests/mochitest/tree/test_map.html
new file mode 100644
index 000000000..6f0b0f2b6
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_map.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML map accessible tree tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ // map used as imagemap, not accessible
+ var accTree =
+ { SECTION: [ ] };
+
+ testAccessibleTree("imagemapcontainer", accTree);
+
+ // map group. Imagemaps are inlines by default, so TEXT_CONTAINER.
+ accTree =
+ { TEXT_CONTAINER: [
+ { PARAGRAPH: [
+ { TEXT_LEAF: [ ] },
+ { LINK: [
+ { TEXT_LEAF: [ ] }
+ ] },
+ { TEXT_LEAF: [ ] },
+ { LINK: [
+ { TEXT_LEAF: [ ] }
+ ] },
+ { TEXT_LEAF: [ ] }
+ ] }
+ ] };
+
+ testAccessibleTree("mapgroup", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Map used for grouping is not accessible under certain circumstances"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=627718">
+ Mozilla Bug 627718
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="imagemapcontainer">
+ <map name="atoz_map">
+ <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b"
+ coords="17,0,30,14" alt="b" shape="rect">
+ <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#a"
+ coords="0,0,13,14" alt="a" shape="rect">
+ </map>
+ </div>
+
+ <img id="imgmap" width="447" height="15"
+ usemap="#atoz_map"
+ src="../letters.gif">
+
+ <map id="mapgroup" title="Navigation Bar" name="mapgroup">
+ <p>
+ [<a href="#how">Bypass navigation bar</a>]
+ [<a href="home.html">Home</a>]
+ </p>
+ </map>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_media.html b/accessible/tests/mochitest/tree/test_media.html
new file mode 100644
index 000000000..b44a7293d
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_media.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=483573
+-->
+<head>
+ <title>HTML5 audio/video tests</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+
+ <script type="application/javascript">
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // test the accessible tree
+
+ var accTree = {
+ role: ROLE_GROUPING,
+ children: [
+ { // start/stop button
+ role: ROLE_PUSHBUTTON,
+ name: "Play",
+ children: []
+ },
+ { // buffer bar
+ role: ROLE_PROGRESSBAR,
+ children: []
+ },
+ { // progress bar
+ role: ROLE_PROGRESSBAR,
+ children: []
+ },
+ { // slider of progress bar
+ role: ROLE_SLIDER,
+ //name: "0:00 of 0:02 elapsed",
+ children: []
+ },
+ { // mute button
+ role: ROLE_PUSHBUTTON,
+ name: "Mute",
+ children: []
+ },
+ { // slider of volume bar
+ role: ROLE_SLIDER,
+ children: []
+ },
+ ]
+ };
+ testAccessibleTree("audio", accTree);
+
+ todo(false, "Enable name test for slider. Fail on Linux.");
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Expose HTML5 video and audio elements' embedded controls through accessibility APIs"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=483573">Mozilla Bug 483573</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <audio id="audio" src="../bug461281.ogg"
+ controls="true"></audio>
+
+ <div id="eventDump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_select.html b/accessible/tests/mochitest/tree/test_select.html
new file mode 100644
index 000000000..151985622
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_select.html
@@ -0,0 +1,139 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML select control tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ var accTree = {
+ role: ROLE_LISTBOX,
+ children: [
+ {
+ role: ROLE_GROUPING,
+ children: [
+ {
+ role: ROLE_STATICTEXT,
+ children: [ ]
+ },
+ {
+ role: ROLE_OPTION,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF
+ }
+ ]
+ },
+ {
+ role: ROLE_OPTION,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF
+ }
+ ]
+ }
+ ]
+ },
+ {
+ role: ROLE_OPTION,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF
+ }
+ ]
+ }
+ ]
+ };
+ testAccessibleTree("listbox", accTree);
+
+ accTree = {
+ role: ROLE_COMBOBOX,
+ children: [
+ {
+ role: ROLE_COMBOBOX_LIST,
+ children: [
+ {
+ role: ROLE_GROUPING,
+ children: [
+ {
+ role: ROLE_STATICTEXT,
+ children: [ ]
+ },
+ {
+ role: ROLE_COMBOBOX_OPTION,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF
+ }
+ ]
+ },
+ {
+ role: ROLE_COMBOBOX_OPTION,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF
+ }
+ ]
+ },
+ ]
+ },
+ {
+ role: ROLE_COMBOBOX_OPTION,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ };
+ testAccessibleTree("combobox", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="remove all the code in #ifdef COMBO_BOX_WITH_THREE_CHILDREN"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=506616">
+ Mozilla Bug 506616
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <select id="listbox" size="4">
+ <optgroup label="Colors">
+ <option>Red</option>
+ <option>Blue</option>
+ </optgroup>
+ <option>Animal</option>
+ </select>
+
+ <select id="combobox">
+ <optgroup label="Colors">
+ <option>Red</option>
+ <option>Blue</option>
+ </optgroup>
+ <option>Animal</option>
+ </select>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_tabbox.xul b/accessible/tests/mochitest/tree/test_tabbox.xul
new file mode 100644
index 000000000..4d4101388
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_tabbox.xul
@@ -0,0 +1,99 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL tabbox hierarchy tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // tabbox
+
+ var accTree = {
+ role: ROLE_PAGETABLIST,
+ children: [
+ {
+ role: ROLE_PAGETAB,
+ children: []
+ },
+ {
+ role: ROLE_PAGETAB,
+ children: []
+ }
+ ]
+ };
+ testAccessibleTree("tabs", accTree);
+
+ accTree = {
+ role: ROLE_PANE,
+ children: [
+ {
+ role: ROLE_PROPERTYPAGE,
+ children: []
+ },
+ {
+ role: ROLE_PROPERTYPAGE,
+ children: []
+ }
+ ]
+ };
+ testAccessibleTree("tabpanels", accTree);
+
+ SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=540389"
+ title=" WARNING: Bad accessible tree!: [tabbrowser tab] ">
+ Mozilla Bug 540389
+ </a><br/>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=552944"
+ title="No relationship between tabs and associated property page in new tabbrowser construct">
+ Mozilla Bug 552944
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <tabbox>
+ <tabs id="tabs">
+ <tab label="tab1"/>
+ <tab label="tab2"/>
+ </tabs>
+ <tabpanels id="tabpanels">
+ <tabpanel/>
+ <tabpanel/>
+ </tabpanels>
+ </tabbox>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/tree/test_tabbrowser.xul b/accessible/tests/mochitest/tree/test_tabbrowser.xul
new file mode 100644
index 000000000..1e25fdb9c
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_tabbrowser.xul
@@ -0,0 +1,255 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL tabbrowser hierarchy tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../browser.js"></script>
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // invoker
+ function testTabHierarchy()
+ {
+ this.eventSeq = [
+ new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 0),
+ new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 1)
+ ];
+
+ this.invoke = function testTabHierarchy_invoke()
+ {
+ var docURIs = ["about:", "about:mozilla"];
+ tabBrowser().loadTabs(docURIs, false, true);
+ }
+
+ this.finalCheck = function testTabHierarchy_finalCheck(aEvent)
+ {
+ ////////////////////
+ // Tab bar
+ ////////////////////
+ var tabsAccTree = {
+ // xul:tabs
+ role: ROLE_PAGETABLIST,
+ children: [
+ // Children depend on application (UI): see below.
+ ]
+ };
+
+ // SeaMonkey and Firefox tabbrowser UIs differ.
+ if (SEAMONKEY) {
+ SimpleTest.ok(true, "Testing SeaMonkey tabbrowser UI.");
+
+ tabsAccTree.children.splice(0, 0,
+ {
+ // xul:toolbarbutton ("Open a new tab")
+ role: ROLE_PUSHBUTTON,
+ children: []
+ },
+ {
+ // xul:tab ("about:")
+ role: ROLE_PAGETAB,
+ children: []
+ },
+ {
+ // tab ("about:mozilla")
+ role: ROLE_PAGETAB,
+ children: []
+ },
+ {
+ // xul:toolbarbutton ("List all tabs")
+ role: ROLE_PUSHBUTTON,
+ children: [
+ {
+ // xul:menupopup
+ role: ROLE_MENUPOPUP,
+ children: []
+ }
+ ]
+ },
+ {
+ // xul:toolbarbutton ("Close current tab")
+ role: ROLE_PUSHBUTTON,
+ children: []
+ }
+ );
+ } else {
+ SimpleTest.ok(true, "Testing Firefox tabbrowser UI.");
+ let newTabChildren = [];
+ if (SpecialPowers.getBoolPref("privacy.userContext.enabled")) {
+ newTabChildren = [
+ {
+ role: ROLE_MENUPOPUP,
+ children: []
+ }
+ ];
+ }
+
+ // NB: The (3) buttons are not visible, unless manually hovered,
+ // probably due to size reduction in this test.
+ tabsAccTree.children.splice(0, 0,
+ {
+ // xul:tab ("about:")
+ role: ROLE_PAGETAB,
+ children: [
+ {
+ // xul:toolbarbutton ("Close Tab")
+ role: ROLE_PUSHBUTTON,
+ children: []
+ }
+ ]
+ },
+ {
+ // tab ("about:mozilla")
+ role: ROLE_PAGETAB,
+ children: [
+ {
+ // xul:toolbarbutton ("Close Tab")
+ role: ROLE_PUSHBUTTON,
+ children: []
+ }
+ ]
+ },
+ {
+ // xul:toolbarbutton ("Open a new tab")
+ role: ROLE_PUSHBUTTON,
+ children: newTabChildren
+ }
+ // "List all tabs" dropdown
+ // XXX: This child(?) is not present in this test.
+ // I'm not sure why (though probably expected).
+ );
+ }
+
+ testAccessibleTree(tabBrowser().tabContainer, tabsAccTree);
+
+ ////////////////////
+ // Tab contents
+ ////////////////////
+ var tabboxAccTree = {
+ // xul:tabpanels
+ role: ROLE_PANE,
+ children: [
+ {
+ // xul:notificationbox
+ role: ROLE_PROPERTYPAGE,
+ children: [
+ {
+ // xul:browser
+ role: ROLE_INTERNAL_FRAME,
+ children: [
+ {
+ // #document ("about:")
+ role: ROLE_DOCUMENT
+ // children: [ ... ] // Ignore document content.
+ }
+ ]
+ }
+ ]
+ },
+ {
+ // notificationbox
+ role: ROLE_PROPERTYPAGE,
+ children: [
+ {
+ // browser
+ role: ROLE_INTERNAL_FRAME,
+ children: [
+ {
+ // #document ("about:mozilla")
+ role: ROLE_DOCUMENT
+ // children: [ ... ] // Ignore document content.
+ }
+ ]
+ }
+ ]
+ },
+ {
+ // notificationbox
+ role: ROLE_PROPERTYPAGE,
+ children: [
+ {
+ // browser
+ role: ROLE_INTERNAL_FRAME,
+ children: [
+ {
+ // #document ("about:newtab" preloaded)
+ role: ROLE_DOCUMENT
+ // children: [ ... ] // Ignore document content.
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ };
+
+ testAccessibleTree(tabBrowser().mTabBox.tabpanels, tabboxAccTree);
+ }
+
+ this.getID = function testTabHierarchy_getID()
+ {
+ return "hierarchy of tabs";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+ //gA11yEventDumpToConsole = true;
+ //enableLogging("tree,verbose,stack");
+
+ var gQueue = null;
+ function doTest()
+ {
+ // Load documents into tabs and wait for docLoadComplete events caused by these
+ // documents load before we start the test.
+ gQueue = new eventQueue();
+
+ gQueue.push(new testTabHierarchy());
+ gQueue.onFinish = function() { closeBrowserWindow(); }
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ openBrowserWindow(doTest);
+ ]]>
+ </script>
+
+ <vbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=540389"
+ title=" WARNING: Bad accessible tree!: [tabbrowser tab] ">
+ Mozilla Bug 540389
+ </a><br/>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=552944"
+ title="No relationship between tabs and associated property page in new tabbrowser construct">
+ Mozilla Bug 552944
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox id="eventdump"></vbox>
+ </vbox>
+
+</window>
diff --git a/accessible/tests/mochitest/tree/test_table.html b/accessible/tests/mochitest/tree/test_table.html
new file mode 100644
index 000000000..bfbd6ae5a
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_table.html
@@ -0,0 +1,282 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML table tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // tables having captions
+
+ // Two captions, first is used, second is ignored.
+ var accTree =
+ { TABLE: [
+ { CAPTION: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "caption"
+ }
+ ] },
+ { ROW: [
+ { COLUMNHEADER: [ { TEXT_LEAF: [ ] } ] },
+ { COLUMNHEADER: [ { TEXT_LEAF: [ ] } ] }
+ ] },
+ { ROW: [
+ { CELL: [ { TEXT_LEAF: [ ] } ] },
+ { CELL: [ { TEXT_LEAF: [ ] } ] }
+ ] },
+ { ROW: [
+ { CELL: [ { TEXT_LEAF: [ ] } ] },
+ { CELL: [ { TEXT_LEAF: [ ] } ] }
+ ] },
+ { ROW: [
+ { CELL: [ { TEXT_LEAF: [ ] } ] },
+ { CELL: [ { TEXT_LEAF: [ ] } ] }
+ ] }
+ ] };
+
+ testAccessibleTree("table", accTree);
+
+ // One caption, empty text, caption is ignored.
+ accTree =
+ { TABLE: [
+ { ROW: [
+ { CELL: [ { TEXT_LEAF: [ ] } ] },
+ { CELL: [ { TEXT_LEAF: [ ] } ] }
+ ] }
+ ] };
+
+ testAccessibleTree("table_caption_empty", accTree);
+
+ // Two captions, first has empty text, both are ignored.
+ accTree =
+ { TABLE: [
+ { ROW: [
+ { CELL: [ { TEXT_LEAF: [ ] } ] },
+ { CELL: [ { TEXT_LEAF: [ ] } ] }
+ ] }
+ ] };
+
+ testAccessibleTree("table_caption_firstempty", accTree);
+
+ // One caption, placed in the end of table. In use.
+ accTree =
+ { TABLE: [
+ { CAPTION: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "caption"
+ }
+ ] },
+ { ROW: [
+ { CELL: [ { TEXT_LEAF: [ ] } ] },
+ { CELL: [ { TEXT_LEAF: [ ] } ] }
+ ] }
+ ] };
+
+ testAccessibleTree("table_caption_intheend", accTree);
+
+ //////////////////////////////////////////////////////////////////////////
+ // table2 (consist of one column)
+
+ accTree = {
+ role: ROLE_TABLE,
+ children: [
+ {
+ role: ROLE_ROW,
+ children: [
+ {
+ role: ROLE_COLUMNHEADER
+ }
+ ]
+ },
+ {
+ role: ROLE_ROW,
+ children: [
+ {
+ role: ROLE_CELL
+ }
+ ]
+ }
+ ]
+ };
+
+ testAccessibleTree("table2", accTree);
+
+ //////////////////////////////////////////////////////////////////////////
+ // table3 (consist of one row)
+
+ accTree = {
+ role: ROLE_TABLE,
+ children: [
+ {
+ role: ROLE_ROW,
+ children: [
+ {
+ role: ROLE_ROWHEADER
+ },
+ {
+ role: ROLE_CELL
+ }
+ ]
+ }
+ ]
+ };
+
+ testAccessibleTree("table3", accTree);
+
+ /////////////////////////////////////////////////////////////////////////
+ // table4 (display: table-row)
+ accTree =
+ { TABLE: [
+ { ROW: [
+ { CELL: [
+ { TEXT_LEAF: [ ] }
+ ] }
+ ] } ]
+ };
+ testAccessibleTree("table4", accTree);
+
+ /////////////////////////////////////////////////////////////////////////
+ // table5 (intermediate accessible for tbody)
+ accTree =
+ { TABLE: [
+ { TEXT_CONTAINER: [
+ { ROW: [
+ { CELL: [
+ { TEXT_LEAF: [ ] }
+ ] }
+ ] }
+ ] } ]
+ };
+ testAccessibleTree("table5", accTree);
+
+ /////////////////////////////////////////////////////////////////////////
+ // log table
+ accTree =
+ { TABLE: [
+ { ROW: [
+ { CELL: [
+ { TEXT_LEAF: [ ] }
+ ] }
+ ] }
+ ] };
+ testAccessibleTree("logtable", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="When a table has only one column per row and that column happens to be a column header its role is exposed wrong"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=529621">
+ Mozilla Bug 529621
+ </a>
+ <a target="_blank"
+ title="when div has display style table-row"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=727722">
+ Mozilla Bug 727722
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <table id="table">
+ <thead>
+ <tr>
+ <th>col1</th><th>col2</th>
+ </tr>
+ </thead>
+ <caption>caption</caption>
+ <tbody>
+ <tr>
+ <td>cell1</td><td>cell2</td>
+ </tr>
+ </tbody>
+ <tr>
+ <td>cell3</td><td>cell4</td>
+ </tr>
+ <caption>caption2</caption>
+ <tfoot>
+ <tr>
+ <td>cell5</td><td>cell6</td>
+ </tr>
+ </tfoot>
+ </table>
+
+ <table id="table_caption_empty">
+ <caption></caption>
+ <tr>
+ <td>cell1</td><td>cell2</td>
+ </tr>
+ </table>
+
+ <table id="table_caption_firstempty">
+ <caption></caption>
+ <tr>
+ <td>cell1</td><td>cell2</td>
+ </tr>
+ <caption>caption</caption>
+ </table>
+
+ <table id="table_caption_intheend">
+ <tr>
+ <td>cell1</td><td>cell2</td>
+ </tr>
+ <caption>caption</caption>
+ </table>
+
+ <table id="table2">
+ <thead>
+ <tr>
+ <th>colheader</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>bla</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table id="table3">
+ <tr>
+ <th>rowheader</th>
+ <td>cell</td>
+ </tr>
+ </table>
+
+ <table id="table4">
+ <div style="display: table-row">
+ <td>cell1</td>
+ </div>
+ </table>
+
+ <table id="table5">
+ <tbody style="display:block;overflow:auto;">
+ <tr>
+ <td>bla</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <table id="logtable" role="log"><tr><td>blah</td></tr></table>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_tree.xul b/accessible/tests/mochitest/tree/test_tree.xul
new file mode 100644
index 000000000..0fd222c42
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_tree.xul
@@ -0,0 +1,182 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL tree hierarchy tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Accessible tree testers
+
+ function getTreeItemAccTree(aTableRole, acolumnCount)
+ {
+ var treeItemRole;
+ switch (aTableRole) {
+ case ROLE_LIST:
+ treeItemRole = ROLE_LISTITEM;
+ break;
+ case ROLE_OUTLINE:
+ treeItemRole = ROLE_OUTLINEITEM;
+ break;
+ case ROLE_TABLE: case ROLE_TREE_TABLE:
+ treeItemRole = ROLE_ROW;
+ break;
+ }
+
+ var accTree = {
+ role: treeItemRole,
+ children: []
+ };
+
+ if (aTableRole == ROLE_TABLE || aTableRole == ROLE_TREE_TABLE) {
+ for (var idx = 0; idx < acolumnCount; idx++) {
+ var cellAccTree = {
+ role: ROLE_GRID_CELL,
+ children: []
+ };
+ accTree.children.push(cellAccTree);
+ }
+ }
+
+ return accTree;
+ }
+
+ function testAccessibleTreeFor(aTree, aRole)
+ {
+ var accTreeForColumns = {
+ role: ROLE_LIST,
+ children: []
+ };
+
+ var accTreeForTree = {
+ role: aRole,
+ children: [
+ accTreeForColumns
+ ]
+ };
+
+ var treeBoxObject = aTree.treeBoxObject;
+ var view = aTree.view;
+ var columnCount = treeBoxObject.columns.count;
+
+ for (var idx = 0; idx < columnCount; idx++)
+ accTreeForColumns.children.push({ COLUMNHEADER: [ ] });
+ if (!aTree.hasAttribute("hidecolumnpicker"))
+ accTreeForColumns.children.push({ PUSHBUTTON: [ { MENUPOPUP: [] } ] });
+
+ for (var idx = 0; idx < view.rowCount; idx++)
+ accTreeForTree.children.push(getTreeItemAccTree(aRole, columnCount));
+
+ testAccessibleTree(aTree, accTreeForTree);
+ }
+
+ /**
+ * Event queue invoker object to test accessible tree for XUL tree element.
+ */
+ function treeChecker(aID, aView, aRole)
+ {
+ this.DOMNode = getNode(aID);
+
+ this.invoke = function invoke()
+ {
+ this.DOMNode.view = aView;
+ }
+ this.check = function check(aEvent)
+ {
+ testAccessibleTreeFor(this.DOMNode, aRole);
+ }
+ this.getID = function getID()
+ {
+ return "Tree testing of " + aID;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "debug";
+ var gQueue = null;
+
+ function doTest()
+ {
+ var gQueue = new eventQueue(EVENT_REORDER);
+
+ gQueue.push(new treeChecker("list", new nsTableTreeView(3), ROLE_LIST));
+ gQueue.push(new treeChecker("tree", new nsTreeTreeView(), ROLE_OUTLINE));
+ gQueue.push(new treeChecker("table", new nsTableTreeView(3), ROLE_TABLE));
+ gQueue.push(new treeChecker("treetable", new nsTreeTreeView(), ROLE_TREE_TABLE));
+
+ gQueue.invoke(); // Will call SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727"
+ title="Reorganize implementation of XUL tree accessibility">
+ Mozilla Bug 503727
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <tree id="list" flex="1" hidecolumnpicker="true">
+ <treecols>
+ <treecol id="col" flex="1" hideheader="true"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <tree id="table" flex="1">
+ <treecols>
+ <treecol id="col1" flex="1" label="column"/>
+ <treecol id="col2" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <tree id="treetable" flex="1">
+ <treecols>
+ <treecol id="col1" flex="1" primary="true" label="column"/>
+ <treecol id="col2" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <vbox id="debug"/>
+ </vbox>
+ </hbox>
+
+</window>
diff --git a/accessible/tests/mochitest/tree/test_txtcntr.html b/accessible/tests/mochitest/tree/test_txtcntr.html
new file mode 100644
index 000000000..80a225ce7
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_txtcntr.html
@@ -0,0 +1,234 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>HTML text containers tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ var accTree = {
+ role: ROLE_SECTION,
+ children: [
+ { // text child
+ role: ROLE_TEXT_LEAF,
+ children: []
+ }
+ ]
+ };
+
+ testAccessibleTree("c1", accTree);
+ testAccessibleTree("c2", accTree);
+
+ accTree = {
+ role: ROLE_SECTION,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "Hello1"
+ },
+ {
+ role: ROLE_WHITESPACE
+ },
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "Hello2"
+ },
+ {
+ role: ROLE_SEPARATOR
+ },
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "Hello3 "
+ },
+ {
+ role: ROLE_PARAGRAPH,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "Hello4 "
+ }
+ ]
+ }
+ ]
+ };
+
+ testAccessibleTree("c3", accTree);
+
+ // contentEditable div
+ accTree = {
+ role: ROLE_SECTION,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "helllo "
+ },
+ {
+ role: ROLE_PARAGRAPH,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "blabla"
+ }
+ ]
+ },
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "hello "
+ }
+ ]
+ };
+
+ testAccessibleTree("c4", accTree);
+
+ // blockquote
+ accTree = {
+ role: ROLE_SECTION,
+ children: [
+ { // block quote
+ role: ROLE_SECTION,
+ children: [
+ { // text child
+ role: ROLE_TEXT_LEAF,
+ name: "Hello",
+ children: []
+ }
+ ]
+ }
+ ]
+ };
+
+ testAccessibleTree("c5", accTree);
+
+ // abbreviation tag
+ accTree = {
+ role: ROLE_SECTION,
+ children: [
+ { // text leaf
+ role: ROLE_TEXT_LEAF,
+ name: "This ",
+ children: []
+ },
+ { // abbr tag
+ role: ROLE_TEXT,
+ name: "accessibility",
+ children: [
+ { // text leaf with actual text
+ role: ROLE_TEXT_LEAF,
+ name: "a11y",
+ children: []
+ }
+ ]
+ },
+ { // text leaf
+ role: ROLE_TEXT_LEAF,
+ name: " test",
+ children: []
+ }
+ ]
+ };
+
+ testAccessibleTree("c6", accTree);
+
+ // acronym tag
+ accTree = {
+ role: ROLE_SECTION,
+ children: [
+ { // text leaf
+ role: ROLE_TEXT_LEAF,
+ name: "This ",
+ children: []
+ },
+ { // acronym tag
+ role: ROLE_TEXT,
+ name: "personal computer",
+ children: [
+ { // text leaf with actual text
+ role: ROLE_TEXT_LEAF,
+ name: "PC",
+ children: []
+ }
+ ]
+ },
+ { // text leaf
+ role: ROLE_TEXT_LEAF,
+ name: " is broken",
+ children: []
+ }
+ ]
+ };
+
+ testAccessibleTree("c7", accTree);
+
+ // only whitespace between images should be exposed
+ accTree = {
+ SECTION: [
+ { GRAPHIC: [] },
+ { TEXT_LEAF: [] },
+ { GRAPHIC: [] }
+ ]
+ };
+ testAccessibleTree("c8", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="overflowed content doesn't expose child text accessibles"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=489306">
+ Mozilla Bug 489306</a>
+ <a target="_blank"
+ title="Create child accessibles for text controls from native anonymous content"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=542824">
+ Mozilla Bug 542824</a>
+ <a target="_blank"
+ title="Update accessible tree on content insertion after layout"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=498015">
+ Mozilla Bug 498015</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="c1" style="width: 100px; height: 100px; overflow: auto;">
+ 1hellohello 2hellohello 3hellohello 4hellohello 5hellohello 6hellohello 7hellohello
+ </div>
+ <div id="c2">
+ 1hellohello 2hellohello 3hellohello 4hellohello 5hellohello 6hellohello 7hellohello
+ </div>
+ <div id="c3">
+ Hello1<br>
+ Hello2<hr>
+ Hello3
+ <p>
+ Hello4
+ </p>
+ </div>
+ <div id="c4" contentEditable="true">
+ helllo <p>blabla</p> hello
+ </div>
+ <div id="c5"><blockquote>Hello</blockquote></div>
+ <div id="c6">This <abbr title="accessibility">a11y</abbr> test</div>
+ <div id="c7">This <acronym title="personal computer">PC</acronym> is broken</div>
+
+ <!-- only whitespace between images should be exposed -->
+ <div id="c8"> <img src="../moz.png"> <img src="../moz.png"> </div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_txtctrl.html b/accessible/tests/mochitest/tree/test_txtctrl.html
new file mode 100644
index 000000000..79613cd41
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_txtctrl.html
@@ -0,0 +1,173 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>HTML text controls tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ // editable div
+ var accTree = {
+ role: ROLE_SECTION,
+ children: [
+ { // text child
+ role: ROLE_TEXT_LEAF,
+ children: []
+ }
+ ]
+ };
+
+ testAccessibleTree("txc1", accTree);
+
+ // input@type="text", value
+ accTree = {
+ role: ROLE_ENTRY,
+ children: [
+ { // text child
+ role: ROLE_TEXT_LEAF,
+ children: []
+ }
+ ]
+ };
+
+ testAccessibleTree("txc2", accTree);
+
+ // input@type="text", no value
+ accTree =
+ { ENTRY: [ ] };
+
+ testAccessibleTree("txc3", accTree);
+
+ // textarea
+ accTree = {
+ role: ROLE_ENTRY,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF // hello1\nhello2 text
+ }
+ ]
+ };
+
+ testAccessibleTree("txc4", accTree);
+
+ // input@type="password"
+ accTree = {
+ role: ROLE_PASSWORD_TEXT,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ children: []
+ }
+ ]
+ };
+
+ testAccessibleTree("txc5", accTree);
+
+ // input@type="tel", value
+ accTree = {
+ role: ROLE_ENTRY,
+ children: [
+ { // text child
+ role: ROLE_TEXT_LEAF,
+ children: []
+ }
+ ]
+ };
+
+ testAccessibleTree("txc6", accTree);
+
+ // input@type="email", value
+ accTree = {
+ role: ROLE_ENTRY,
+ children: [
+ { // text child
+ role: ROLE_TEXT_LEAF,
+ children: []
+ }
+ ]
+ };
+
+ testAccessibleTree("txc7", accTree);
+
+ // input@type="search", value
+ accTree = {
+ role: ROLE_ENTRY,
+ children: [
+ { // text child
+ role: ROLE_TEXT_LEAF,
+ children: []
+ }
+ ]
+ };
+
+ testAccessibleTree("txc8", accTree);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="overflowed content doesn't expose child text accessibles"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=489306">
+ Mozilla Bug 489306
+ </a><br>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=542824"
+ title="Create child accessibles for text controls from native anonymous content">
+ Mozilla Bug 542824
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=625652"
+ title="Make sure accessible tree is correct when rendered text is changed">
+ Mozilla Bug 625652
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="txc1" contentEditable="true">
+ 1hellohello
+ </div>
+ <input id="txc2" value="hello">
+ <input id="txc3">
+ <textarea id="txc4">
+ hello1
+ hello2
+ </textarea>
+ <input id="txc5" type="password" value="hello">
+ <input id="txc6" type="tel" value="4167771234">
+
+ Email Address:
+ <input id="txc7" type="email" list="contacts" value="xyzzy">
+ <datalist id="contacts">
+ <option>xyzzy@plughs.com</option>
+ <option>nobody@mozilla.org</option>
+ </datalist>
+
+ </br>Search for:
+ <input id="txc8" type="search" list="searchhisty" value="Gamma">
+ <datalist id="searchhisty">
+ <option>Gamma Rays</option>
+ <option>Gamma Ray Bursts</option>
+ </datalist>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/tree/test_txtctrl.xul b/accessible/tests/mochitest/tree/test_txtctrl.xul
new file mode 100644
index 000000000..cb43b498f
--- /dev/null
+++ b/accessible/tests/mochitest/tree/test_txtctrl.xul
@@ -0,0 +1,219 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL textbox and textarea hierarchy tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ //gA11yEventDumpToConsole = true;
+ //enableLogging("tree,verbose"); // debug stuff
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function doTest()
+ {
+ //////////////////////////////////////////////////////////////////////////
+ // textboxes
+
+ var accTree =
+ { SECTION: [
+ { ENTRY: [ { TEXT_LEAF: [] } ] },
+ { MENUPOPUP: [] }
+ ] };
+
+ // default textbox
+ testAccessibleTree("txc", accTree);
+
+ // multiline
+ testAccessibleTree("txc_multiline", accTree);
+
+ //////////////////////////////////////////////////////////////////////////
+ // search textbox
+ accTree =
+ { SECTION: [
+ { ENTRY: [ { TEXT_LEAF: [] } ] },
+ { MENUPOPUP: [] }
+ ] };
+ testAccessibleTree("txc_search", accTree);
+
+ //////////////////////////////////////////////////////////////////////////
+ // search textbox with search button
+
+ if (MAC) {
+ accTree =
+ { SECTION: [
+ { ENTRY: [ { TEXT_LEAF: [] } ] },
+ { MENUPOPUP: [] }
+ ] };
+ } else {
+ accTree =
+ { SECTION: [
+ { ENTRY: [ { TEXT_LEAF: [] } ] },
+ { PUSHBUTTON: [] },
+ { MENUPOPUP: [] }
+ ] };
+ }
+
+ testAccessibleTree("txc_search_searchbutton", accTree);
+
+ //////////////////////////////////////////////////////////////////////////
+ // number textbox
+
+ accTree =
+ { SECTION: [
+ { ENTRY: [ { TEXT_LEAF: [] } ] },
+ { MENUPOPUP: [] },
+ { PUSHBUTTON: [] },
+ { PUSHBUTTON: [] }
+ ] };
+
+ testAccessibleTree("txc_number", accTree);
+
+ //////////////////////////////////////////////////////////////////////////
+ // password textbox
+
+ accTree =
+ { SECTION: [
+ { PASSWORD_TEXT: [ { TEXT_LEAF: [] } ] },
+ { MENUPOPUP: [] }
+ ] };
+
+ testAccessibleTree("txc_password", accTree);
+
+ //////////////////////////////////////////////////////////////////////////
+ // autocomplete textbox
+
+ accTree = {
+ // textbox
+ role: ROLE_AUTOCOMPLETE,
+ children: [
+ {
+ // html:input
+ role: ROLE_ENTRY,
+ children: [
+ {
+ // #text
+ role: ROLE_TEXT_LEAF,
+ children: []
+ }
+ ]
+ },
+ {
+ // xul:menupopup
+ role: ROLE_COMBOBOX_LIST,
+ children: []
+ }
+ ]
+ };
+
+ function test_AutocompleteControl() {
+ testAccessibleTree("txc_autocomplete", accTree);
+ SimpleTest.finish();
+ }
+
+ // XPFE and Toolkit autocomplete widgets differ.
+ var txc = document.getElementById("txc_autocomplete");
+ if ("clearResults" in txc) {
+ SimpleTest.ok(true, "Testing (Old) XPFE autocomplete widget.");
+
+ // Popup is always created. (See code below.)
+
+ accTree.children.push(
+ {
+ // xul:panel
+ role: ROLE_COMBOBOX_LIST,
+ children: [
+ {
+ // xul:tree
+ role: ROLE_TABLE,
+ children: [
+ {
+ // xul:treecols
+ role: ROLE_LIST,
+ children: [
+ {
+ // xul:treecol
+ role: ROLE_COLUMNHEADER,
+ children: []
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ );
+ test_AutocompleteControl();
+
+ } else {
+ SimpleTest.ok(true, "Testing (New) Toolkit autocomplete widget.");
+
+ // Dumb access to trigger popup lazy creation.
+ dump("Trigget popup lazy creation");
+ waitForEvent(EVENT_REORDER, txc, test_AutocompleteControl);
+ txc.popup;
+
+ accTree.children.push(
+ {
+ role: ROLE_LIST,
+ children: [
+ {
+ role: ROLE_LIST,
+ children: [
+ {
+ role: ROLE_COLUMNHEADER,
+ children: []
+ }
+ ]
+ }
+ ]
+ }
+ );
+ }
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=542824"
+ title="Create child accessibles for text controls from native anonymous content">
+ Mozilla Bug 542824
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <textbox id="txc" value="hello"/>
+ <textbox id="txc_search" type="search" value="hello"/>
+ <textbox id="txc_search_searchbutton" searchbutton="true" type="search" value="hello"/>
+ <textbox id="txc_number" type="number" value="44"/>
+ <textbox id="txc_password" type="password" value="hello"/>
+ <textbox id="txc_multiline" multiline="true" value="hello"/>
+ <textbox id="txc_autocomplete" type="autocomplete" value="hello"/>
+ </vbox>
+ </hbox>
+
+</window>
diff --git a/accessible/tests/mochitest/tree/wnd.xul b/accessible/tests/mochitest/tree/wnd.xul
new file mode 100644
index 000000000..3b87cb5e0
--- /dev/null
+++ b/accessible/tests/mochitest/tree/wnd.xul
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Empty Window">
+
+</window>
+
diff --git a/accessible/tests/mochitest/treeupdate/a11y.ini b/accessible/tests/mochitest/treeupdate/a11y.ini
new file mode 100644
index 000000000..d725ebff3
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/a11y.ini
@@ -0,0 +1,41 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/letters.gif
+ !/accessible/tests/mochitest/moz.png
+
+[test_ariadialog.html]
+[test_ariaowns.html]
+[test_bug852150.xhtml]
+[test_bug883708.xhtml]
+[test_bug884251.xhtml]
+[test_bug895082.html]
+[test_bug1040735.html]
+[test_bug1100602.html]
+[test_bug1175913.html]
+[test_bug1189277.html]
+[test_bug1276857.html]
+[test_canvas.html]
+[test_colorpicker.xul]
+[test_contextmenu.xul]
+[test_cssoverflow.html]
+[test_deck.xul]
+[test_doc.html]
+[test_gencontent.html]
+[test_general.html]
+[test_hidden.html]
+[test_imagemap.html]
+skip-if = buildapp == "mulet"
+[test_list.html]
+[test_list_editabledoc.html]
+[test_listbox.xul]
+[test_menu.xul]
+[test_menubutton.xul]
+[test_optgroup.html]
+[test_recreation.html]
+[test_select.html]
+[test_shutdown.xul]
+[test_table.html]
+[test_textleaf.html]
+[test_visibility.html]
+[test_whitespace.html]
diff --git a/accessible/tests/mochitest/treeupdate/test_ariadialog.html b/accessible/tests/mochitest/treeupdate/test_ariadialog.html
new file mode 100644
index 000000000..559431626
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_ariadialog.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Table creation in ARIA dialog test</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function showARIADialog(aID)
+ {
+ this.node = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, this.node)
+ ];
+
+ this.invoke = function showARIADialog_invoke()
+ {
+ getNode("dialog").style.display = "block";
+ getNode("table").style.visibility = "visible";
+ getNode("a").textContent = "link";
+ getNode("input").value = "hello";
+ getNode("input").focus();
+ }
+
+ this.finalCheck = function showARIADialog_finalCheck()
+ {
+ var tree = {
+ role: ROLE_DIALOG,
+ children: [
+ {
+ role: ROLE_PUSHBUTTON,
+ children: [ { role: ROLE_TEXT_LEAF } ]
+ },
+ {
+ role: ROLE_ENTRY
+ }
+ ]
+ };
+ testAccessibleTree(aID, tree);
+ }
+
+ this.getID = function showARIADialog_getID()
+ {
+ return "show ARIA dialog";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ //enableLogging("tree");
+ gQueue = new eventQueue();
+
+ // make the accessible an inaccessible
+ gQueue.push(new showARIADialog("dialog"));
+
+ gQueue.invoke(); // SimpleTest.finish() will be called in the end
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Rework accessible tree update code"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275">
+ Mozilla Bug 570275
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="dialog" role="dialog" style="display: none;">
+ <table id="table" role="presentation"
+ style="display: block; position: fixed; top: 88px; left: 312.5px; z-index: 10010; visibility: hidden;">
+ <tbody>
+ <tr>
+ <td role="presentation">
+ <div role="presentation">
+ <a id="a" role="button">text</a>
+ </div>
+ <input id="input">
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_ariaowns.html b/accessible/tests/mochitest/treeupdate/test_ariaowns.html
new file mode 100644
index 000000000..44fc22a2d
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_ariaowns.html
@@ -0,0 +1,693 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>@aria-owns attribute testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+ ////////////////////////////////////////////////////////////////////////////
+
+ function changeARIAOwns()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode("t1_button")),
+ // no hide for t1_subdiv because it is contained by hidden t1_checkbox
+ new invokerChecker(EVENT_HIDE, getNode("t1_checkbox")),
+ new invokerChecker(EVENT_SHOW, getNode("t1_checkbox")),
+ new invokerChecker(EVENT_SHOW, getNode("t1_button")),
+ new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")),
+ new invokerChecker(EVENT_REORDER, getNode("t1_container"))
+ ];
+
+ this.invoke = function setARIAOwns_invoke()
+ {
+ // children are swapped by ARIA owns
+ var tree =
+ { SECTION: [
+ { CHECKBUTTON: [
+ { SECTION: [] }
+ ] },
+ { PUSHBUTTON: [ ] }
+ ] };
+ testAccessibleTree("t1_container", tree);
+
+ getNode("t1_container").
+ setAttribute("aria-owns", "t1_button t1_subdiv");
+ }
+
+ this.finalCheck = function setARIAOwns_finalCheck()
+ {
+ // children are swapped again, button and subdiv are appended to
+ // the children.
+ var tree =
+ { SECTION: [
+ { CHECKBUTTON: [ ] }, // checkbox, native order
+ { PUSHBUTTON: [ ] }, // button, rearranged by ARIA own
+ { SECTION: [ ] } // subdiv from the subtree, ARIA owned
+ ] };
+ testAccessibleTree("t1_container", tree);
+ }
+
+ this.getID = function setARIAOwns_getID()
+ {
+ return "Change @aria-owns attribute";
+ }
+ }
+
+ function removeARIAOwns()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode("t1_button")),
+ new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")),
+ new orderChecker(),
+ new asyncInvokerChecker(EVENT_SHOW, getNode("t1_button")),
+ new asyncInvokerChecker(EVENT_SHOW, getNode("t1_subdiv")),
+ new orderChecker(),
+ new invokerChecker(EVENT_REORDER, getNode("t1_container")),
+ new unexpectedInvokerChecker(EVENT_REORDER, getNode("t1_checkbox"))
+ ];
+
+ this.invoke = function removeARIAOwns_invoke()
+ {
+ getNode("t1_container").removeAttribute("aria-owns");
+ }
+
+ this.finalCheck = function removeARIAOwns_finalCheck()
+ {
+ // children follow the DOM order
+ var tree =
+ { SECTION: [
+ { PUSHBUTTON: [ ] },
+ { CHECKBUTTON: [
+ { SECTION: [] }
+ ] }
+ ] };
+ testAccessibleTree("t1_container", tree);
+ }
+
+ this.getID = function removeARIAOwns_getID()
+ {
+ return "Remove @aria-owns attribute";
+ }
+ }
+
+ function setARIAOwns()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode("t1_button")),
+ new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")),
+ new invokerChecker(EVENT_SHOW, getNode("t1_button")),
+ new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")),
+ new invokerChecker(EVENT_REORDER, getNode("t1_container"))
+ ];
+
+ this.invoke = function setARIAOwns_invoke()
+ {
+ getNode("t1_container").
+ setAttribute("aria-owns", "t1_button t1_subdiv");
+ }
+
+ this.finalCheck = function setARIAOwns_finalCheck()
+ {
+ // children are swapped again, button and subdiv are appended to
+ // the children.
+ var tree =
+ { SECTION: [
+ { CHECKBUTTON: [ ] }, // checkbox
+ { PUSHBUTTON: [ ] }, // button, rearranged by ARIA own
+ { SECTION: [ ] } // subdiv from the subtree, ARIA owned
+ ] };
+ testAccessibleTree("t1_container", tree);
+ }
+
+ this.getID = function setARIAOwns_getID()
+ {
+ return "Set @aria-owns attribute";
+ }
+ }
+
+ function addIdToARIAOwns()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode("t1_group")),
+ new invokerChecker(EVENT_SHOW, getNode("t1_group")),
+ new invokerChecker(EVENT_REORDER, document)
+ ];
+
+ this.invoke = function addIdToARIAOwns_invoke()
+ {
+ getNode("t1_container").
+ setAttribute("aria-owns", "t1_button t1_subdiv t1_group");
+ }
+
+ this.finalCheck = function addIdToARIAOwns_finalCheck()
+ {
+ // children are swapped again, button and subdiv are appended to
+ // the children.
+ var tree =
+ { SECTION: [
+ { CHECKBUTTON: [ ] }, // t1_checkbox
+ { PUSHBUTTON: [ ] }, // button, t1_button
+ { SECTION: [ ] }, // subdiv from the subtree, t1_subdiv
+ { GROUPING: [ ] } // group from outside, t1_group
+ ] };
+ testAccessibleTree("t1_container", tree);
+ }
+
+ this.getID = function addIdToARIAOwns_getID()
+ {
+ return "Add id to @aria-owns attribute value";
+ }
+ }
+
+ function appendEl()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getNode, "t1_child3"),
+ new invokerChecker(EVENT_REORDER, getNode("t1_container"))
+ ];
+
+ this.invoke = function appendEl_invoke()
+ {
+ var div = document.createElement("div");
+ div.setAttribute("id", "t1_child3");
+ div.setAttribute("role", "radio")
+ getNode("t1_container").appendChild(div);
+ }
+
+ this.finalCheck = function appendEl_finalCheck()
+ {
+ // children are invalidated, they includes aria-owns swapped kids and
+ // newly inserted child.
+ var tree =
+ { SECTION: [
+ { CHECKBUTTON: [ ] }, // existing explicit, t1_checkbox
+ { RADIOBUTTON: [ ] }, // new explicit, t1_child3
+ { PUSHBUTTON: [ ] }, // ARIA owned, t1_button
+ { SECTION: [ ] }, // ARIA owned, t1_subdiv
+ { GROUPING: [ ] } // ARIA owned, t1_group
+ ] };
+ testAccessibleTree("t1_container", tree);
+ }
+
+ this.getID = function appendEl_getID()
+ {
+ return "Append child under @aria-owns element";
+ }
+ }
+
+ function removeEl()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode, "t1_checkbox"),
+ new invokerChecker(EVENT_SHOW, getNode, "t1_checkbox"),
+ new invokerChecker(EVENT_REORDER, getNode("t1_container"))
+ ];
+
+ this.invoke = function removeEl_invoke()
+ {
+ // remove a container of t1_subdiv
+ getNode("t1_span").parentNode.removeChild(getNode("t1_span"));
+ }
+
+ this.finalCheck = function removeEl_finalCheck()
+ {
+ // subdiv should go away
+ var tree =
+ { SECTION: [
+ { CHECKBUTTON: [ ] }, // explicit, t1_checkbox
+ { RADIOBUTTON: [ ] }, // explicit, t1_child3
+ { PUSHBUTTON: [ ] }, // ARIA owned, t1_button
+ { GROUPING: [ ] } // ARIA owned, t1_group
+ ] };
+ testAccessibleTree("t1_container", tree);
+ }
+
+ this.getID = function removeEl_getID()
+ {
+ return "Remove a container of ARIA owned element";
+ }
+ }
+
+ function removeId()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode("t1_group")),
+ new invokerChecker(EVENT_SHOW, getNode("t1_group")),
+ new invokerChecker(EVENT_REORDER, document)
+ ];
+
+ this.invoke = function removeId_invoke()
+ {
+ getNode("t1_group").removeAttribute("id");
+ }
+
+ this.finalCheck = function removeId_finalCheck()
+ {
+ var tree =
+ { SECTION: [
+ { CHECKBUTTON: [ ] },
+ { RADIOBUTTON: [ ] },
+ { PUSHBUTTON: [ ] } // ARIA owned, t1_button
+ ] };
+ testAccessibleTree("t1_container", tree);
+ }
+
+ this.getID = function removeId_getID()
+ {
+ return "Remove ID from ARIA owned element";
+ }
+ }
+
+ function setId()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode("t1_grouptmp")),
+ new invokerChecker(EVENT_SHOW, getNode("t1_grouptmp")),
+ new invokerChecker(EVENT_REORDER, document)
+ ];
+
+ this.invoke = function setId_invoke()
+ {
+ getNode("t1_grouptmp").setAttribute("id", "t1_group");
+ }
+
+ this.finalCheck = function setId_finalCheck()
+ {
+ var tree =
+ { SECTION: [
+ { CHECKBUTTON: [ ] },
+ { RADIOBUTTON: [ ] },
+ { PUSHBUTTON: [ ] }, // ARIA owned, t1_button
+ { GROUPING: [ ] } // ARIA owned, t1_group, previously t1_grouptmp
+ ] };
+ testAccessibleTree("t1_container", tree);
+ }
+
+ this.getID = function setId_getID()
+ {
+ return "Set ID that is referred by ARIA owns";
+ }
+ }
+
+ /**
+ * Remove an accessible DOM element containing an element referred by
+ * ARIA owns.
+ */
+ function removeA11eteiner()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, getNode("t2_container1"))
+ ];
+
+ this.invoke = function removeA11eteiner_invoke()
+ {
+ var tree =
+ { SECTION: [
+ { CHECKBUTTON: [ ] } // ARIA owned, 't2_owned'
+ ] };
+ testAccessibleTree("t2_container1", tree);
+
+ getNode("t2_container2").removeChild(getNode("t2_container3"));
+ }
+
+ this.finalCheck = function removeA11eteiner_finalCheck()
+ {
+ var tree =
+ { SECTION: [
+ ] };
+ testAccessibleTree("t2_container1", tree);
+ }
+
+ this.getID = function removeA11eteiner_getID()
+ {
+ return "Remove an accessible DOM element containing an element referred by ARIA owns";
+ }
+ }
+
+ /**
+ * Steal an element from other ARIA owns element. This use case guarantees
+ * that result of setAttribute/removeAttribute doesn't depend on their order.
+ */
+ function stealFromOtherARIAOwns()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, getNode("t3_container2"))
+ ];
+
+ this.invoke = function stealFromOtherARIAOwns_invoke()
+ {
+ getNode("t3_container2").setAttribute("aria-owns", "t3_child");
+ }
+
+ this.finalCheck = function stealFromOtherARIAOwns_finalCheck()
+ {
+ var tree =
+ { SECTION: [
+ ] };
+ testAccessibleTree("t3_container1", tree);
+
+ tree =
+ { SECTION: [
+ { CHECKBUTTON: [
+ ] }
+ ] };
+ testAccessibleTree("t3_container2", tree);
+ }
+
+ this.getID = function stealFromOtherARIAOwns_getID()
+ {
+ return "Steal an element from other ARIA owns element";
+ }
+ }
+
+ function appendElToRecacheChildren()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, getNode("t3_container2"))
+ ];
+
+ this.invoke = function appendElToRecacheChildren_invoke()
+ {
+ var div = document.createElement("div");
+ div.setAttribute("role", "radio")
+ getNode("t3_container2").appendChild(div);
+ }
+
+ this.finalCheck = function appendElToRecacheChildren_finalCheck()
+ {
+ var tree =
+ { SECTION: [
+ ] };
+ testAccessibleTree("t3_container1", tree);
+
+ tree =
+ { SECTION: [
+ { RADIOBUTTON: [ ] },
+ { CHECKBUTTON: [ ] } // ARIA owned
+ ] };
+ testAccessibleTree("t3_container2", tree);
+ }
+
+ this.getID = function appendElToRecacheChildren_getID()
+ {
+ return "Append a child under @aria-owns element to trigger children recache";
+ }
+ }
+
+ function showHiddenElement()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, getNode("t4_container1"))
+ ];
+
+ this.invoke = function showHiddenElement_invoke()
+ {
+ var tree =
+ { SECTION: [
+ { RADIOBUTTON: [] }
+ ] };
+ testAccessibleTree("t4_container1", tree);
+
+ getNode("t4_child1").style.display = "block";
+ }
+
+ this.finalCheck = function showHiddenElement_finalCheck()
+ {
+ var tree =
+ { SECTION: [
+ { CHECKBUTTON: [] },
+ { RADIOBUTTON: [] }
+ ] };
+ testAccessibleTree("t4_container1", tree);
+ }
+
+ this.getID = function showHiddenElement_getID()
+ {
+ return "Show hidden ARIA owns referred element";
+ }
+ }
+
+ function rearrangeARIAOwns(aContainer, aAttr, aIdList, aRoleList)
+ {
+ this.eventSeq = [];
+ for (var id of aIdList) {
+ this.eventSeq.push(new invokerChecker(EVENT_HIDE, getNode(id)));
+ }
+
+ for (var id of aIdList) {
+ this.eventSeq.push(new invokerChecker(EVENT_SHOW, getNode(id)));
+ }
+ this.eventSeq.push(new invokerChecker(EVENT_REORDER, getNode(aContainer)));
+
+ this.invoke = function rearrangeARIAOwns_invoke()
+ {
+ getNode(aContainer).setAttribute("aria-owns", aAttr);
+ }
+
+ this.finalCheck = function rearrangeARIAOwns_finalCheck()
+ {
+ var tree = { SECTION: [ ] };
+ for (var role of aRoleList) {
+ var ch = {};
+ ch[role] = [];
+ tree["SECTION"].push(ch);
+ }
+ testAccessibleTree(aContainer, tree);
+ }
+
+ this.getID = function rearrangeARIAOwns_getID()
+ {
+ return `Rearrange @aria-owns attribute to '${aAttr}'`;
+ }
+ }
+
+ function removeNotARIAOwnedEl(aContainer, aChild)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, aContainer)
+ ];
+
+ this.invoke = function removeNotARIAOwnedEl_invoke()
+ {
+ var tree = {
+ SECTION: [
+ { TEXT_LEAF: [ ] },
+ { GROUPING: [ ] }
+ ]
+ };
+ testAccessibleTree(aContainer, tree);
+
+ getNode(aContainer).removeChild(getNode(aChild));
+ }
+
+ this.finalCheck = function removeNotARIAOwnedEl_finalCheck()
+ {
+ var tree = {
+ SECTION: [
+ { GROUPING: [ ] }
+ ]
+ };
+ testAccessibleTree(aContainer, tree);
+ }
+
+ this.getID = function removeNotARIAOwnedEl_getID()
+ {
+ return `remove not ARIA owned child`;
+ }
+ }
+
+ function setARIAOwnsOnElToRemove(aParent, aChild)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getAccessible(aParent))
+ ];
+
+ this.invoke = function setARIAOwnsOnElToRemove_invoke()
+ {
+ getNode(aChild).setAttribute("aria-owns", "no_id");
+ getNode(aParent).removeChild(getNode(aChild));
+ getNode(aParent).parentNode.removeChild(getNode(aParent));
+ }
+
+ this.getID = function setARIAOwnsOnElToRemove_getID()
+ {
+ return `set ARIA owns on an element, and then remove it, and then remove its parent`;
+ }
+ }
+
+ /**
+ * Set ARIA owns on inaccessible span element that contains
+ * accessible children. This will move children from the container for
+ * the span.
+ */
+ function test8()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, "t8_container")
+ ];
+
+ this.invoke = function test8_invoke()
+ {
+ var tree =
+ { SECTION: [
+ { PUSHBUTTON: [] },
+ { ENTRY: [] },
+ { ENTRY: [] },
+ { ENTRY: [] }
+ ] };
+ testAccessibleTree("t8_container", tree);
+
+ getNode(t8_container).setAttribute("aria-owns", "t8_span t8_button");
+ }
+
+ this.finalCheck = function test8_finalCheck()
+ {
+ var tree =
+ { SECTION: [
+ { TEXT: [
+ { ENTRY: [] },
+ { ENTRY: [] },
+ { ENTRY: [] }
+ ] },
+ { PUSHBUTTON: [] }
+ ] };
+ testAccessibleTree("t8_container", tree);
+ }
+
+ this.getID = function test8_getID()
+ {
+ return `Set ARIA owns on inaccessible span element that contains accessible children`;
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+ ////////////////////////////////////////////////////////////////////////////
+
+ //gA11yEventDumpToConsole = true;
+ //enableLogging("tree,eventTree,verbose"); // debug stuff
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ // test1
+ gQueue.push(new changeARIAOwns());
+ gQueue.push(new removeARIAOwns());
+ gQueue.push(new setARIAOwns());
+ gQueue.push(new addIdToARIAOwns());
+ gQueue.push(new appendEl());
+ gQueue.push(new removeEl());
+ gQueue.push(new removeId());
+ gQueue.push(new setId());
+
+ // test2
+ gQueue.push(new removeA11eteiner());
+
+ // test3
+ gQueue.push(new stealFromOtherARIAOwns());
+ gQueue.push(new appendElToRecacheChildren());
+
+ // test4
+ gQueue.push(new showHiddenElement());
+
+ // test5
+ gQueue.push(new rearrangeARIAOwns(
+ "t5_container", "t5_checkbox t5_radio t5_button",
+ [ "t5_checkbox", "t5_radio", "t5_button" ],
+ [ "CHECKBUTTON", "RADIOBUTTON", "PUSHBUTTON" ]));
+ gQueue.push(new rearrangeARIAOwns(
+ "t5_container", "t5_radio t5_button t5_checkbox",
+ [ "t5_radio", "t5_button" ],
+ [ "RADIOBUTTON", "PUSHBUTTON", "CHECKBUTTON" ]));
+
+ gQueue.push(new removeNotARIAOwnedEl("t6_container", "t6_span"));
+
+ gQueue.push(new setARIAOwnsOnElToRemove("t7_parent", "t7_child"));
+
+ gQueue.push(new test8());
+
+ gQueue.invoke(); // SimpleTest.finish() will be called in the end
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+
+ </script>
+</head>
+
+<body>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="t1_container" aria-owns="t1_checkbox t1_button">
+ <div role="button" id="t1_button"></div>
+ <div role="checkbox" id="t1_checkbox">
+ <span id="t1_span">
+ <div id="t1_subdiv"></div>
+ </span>
+ </div>
+ </div>
+ <div id="t1_group" role="group"></div>
+ <div id="t1_grouptmp" role="group"></div>
+
+ <div id="t2_container1" aria-owns="t2_owned"></div>
+ <div id="t2_container2">
+ <div id="t2_container3"><div id="t2_owned" role="checkbox"></div></div>
+ </div>
+
+ <div id="t3_container1" aria-owns="t3_child"></div>
+ <div id="t3_child" role="checkbox"></div>
+ <div id="t3_container2"></div>
+
+ <div id="t4_container1" aria-owns="t4_child1 t4_child2"></div>
+ <div id="t4_container2">
+ <div id="t4_child1" style="display:none" role="checkbox"></div>
+ <div id="t4_child2" role="radio"></div>
+ </div>
+
+ <div id="t5_container">
+ <div role="button" id="t5_button"></div>
+ <div role="checkbox" id="t5_checkbox"></div>
+ <div role="radio" id="t5_radio"></div>
+ </div>
+
+ <div id="t6_container" aria-owns="t6_fake">
+ <span id="t6_span">hey</span>
+ </div>
+ <div id="t6_fake" role="group"></div>
+
+ <div id="t7_container">
+ <div id="t7_parent">
+ <div id="t7_child"></div>
+ </div>
+ </div>
+
+ <div id="t8_container">
+ <input id="t8_button" type="button"><span id="t8_span"><input><input><input></span>
+ </div>
+</body>
+
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_bug1040735.html b/accessible/tests/mochitest/treeupdate/test_bug1040735.html
new file mode 100644
index 000000000..527f23197
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_bug1040735.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Adopt DOM node from anonymous subtree</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="../common.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ document.body.appendChild(document.getElementById("mw_a"));
+ setTimeout(function() { ok(true, "no crash and assertions"); SimpleTest.finish(); } , 0);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1040735"
+ title="Bug 1040735 - DOM node reinsertion under anonymous content may trigger a11y child adoption">
+ Bug 1040735</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <marquee>
+ <div id="mw_a" style="visibility: hidden;">
+ <div style="visibility: visible;" id="mw_inside"></div>
+ </div>
+ </marquee>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_bug1100602.html b/accessible/tests/mochitest/treeupdate/test_bug1100602.html
new file mode 100644
index 000000000..1637e5057
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_bug1100602.html
@@ -0,0 +1,114 @@
+<html>
+
+<head>
+ <title>Test hide/show events for HTMLListBulletAccessibles on list restyle</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ /**
+ * Change list style type to none.
+ */
+ function hideBullet()
+ {
+ this.eventSeq = [];
+ this.liAcc = getAccessible("list_element");
+ this.bullet = this.liAcc.firstChild;
+
+ this.eventSeq.push(new invokerChecker(EVENT_HIDE, this.bullet));
+ this.eventSeq.push(new invokerChecker(EVENT_REORDER, this.liAcc));
+
+ this.invoke = function hideBullet_invoke()
+ {
+ getNode("list").setAttribute("style", "list-style-type: none;");
+ }
+
+ this.finalCheck = function hideBullet_finalCheck()
+ {
+ is(this.liAcc.name, "list element",
+ "Check that first child of LI is not a bullet.");
+ }
+
+ this.getID = function hideBullet_getID()
+ {
+ return "Hide bullet by setting style to none";
+ }
+ }
+
+ /**
+ * Change list style type to circles.
+ */
+ function showBullet()
+ {
+ this.eventSeq = [];
+ this.liAcc = getAccessible("list_element");
+
+ this.eventSeq.push(new invokerChecker(EVENT_SHOW,
+ function(aNode) { return aNode.firstChild; },
+ this.liAcc));
+ this.eventSeq.push(new invokerChecker(EVENT_REORDER, this.liAcc));
+
+ this.invoke = function showBullet_invoke()
+ {
+ getNode("list").setAttribute("style", "list-style-type: circle;");
+ }
+
+ this.finalCheck = function showBullet_finalCheck()
+ {
+ is(this.liAcc.name, "◦ list element",
+ "Check that first child of LI is a circle bullet.");
+ }
+
+ this.getID = function showBullet_getID()
+ {
+ return "Show bullet by setting style to circle";
+ }
+ }
+
+ var gQueue = null;
+ function doTest()
+ {
+
+ var list = getNode("list");
+ list.setAttribute("style", "list-style-type: circle;");
+
+ gQueue = new eventQueue();
+ gQueue.push(new hideBullet());
+ gQueue.push(new showBullet());
+ gQueue.invoke(); // SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1100602"
+ title="[e10s] crash in mozilla::a11y::ProxyAccessible::Shutdown()">
+ Mozilla Bug 1100602
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <ol id="list">
+ <li id="list_element">list element</li>
+ </ol>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_bug1175913.html b/accessible/tests/mochitest/treeupdate/test_bug1175913.html
new file mode 100644
index 000000000..5dab9b5a8
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_bug1175913.html
@@ -0,0 +1,105 @@
+<html>
+
+<head>
+ <title>Test hide/show events on event listener changes</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ function dummyListener() {}
+
+ function testAddListener()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getNode("parent")),
+ ];
+
+ this.invoke = function testAddListener_invoke()
+ {
+ is(getAccessible("parent", null, null, DONOTFAIL_IF_NO_ACC), null, "Check that parent is not accessible.");
+ is(getAccessible("child", null, null, DONOTFAIL_IF_NO_ACC), null, "Check that child is not accessible.");
+ getNode("parent").addEventListener("click", dummyListener);
+ }
+
+ this.finalCheck = function testAddListener_finalCheck()
+ {
+ var tree = { TEXT: [] };
+ testAccessibleTree("parent", tree);
+ }
+
+ this.getID = function testAddListener_getID()
+ {
+ return "Test that show event is sent when click listener is added";
+ }
+ }
+
+ function testRemoveListener()
+ {
+ this.eventSeq = [
+ new unexpectedInvokerChecker(EVENT_HIDE, getNode("parent")),
+ ];
+
+ this.invoke = function testRemoveListener_invoke()
+ {
+ getNode("parent").removeEventListener("click", dummyListener);
+ }
+
+ this.finalCheck = function testRemoveListener_finalCheck()
+ {
+ ok(getAccessible("parent", null, null, DONOTFAIL_IF_NO_ACC),
+ "Parent stays accessible after click event listener is removed");
+ ok(!getAccessible("child", null, null, DONOTFAIL_IF_NO_ACC),
+ "Child stays inaccessible");
+ }
+
+ this.getID = function testRemoveListener_getID()
+ {
+ return "Test that hide event is sent when click listener is removed";
+ }
+ }
+
+ var gQueue = null;
+ function doTest()
+ {
+ gQueue = new eventQueue();
+ gQueue.push(new testAddListener());
+ gQueue.push(new testRemoveListener());
+ gQueue.invoke(); // SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1175913"
+ title="Crash in mozilla::a11y::DocAccessibleParent::RemoveAccessible(ProxyAccessible* aAccessible)">
+ Mozilla Bug 1175913
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <span id="parent">
+ <span id="child">
+ </span>
+ </span>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_bug1189277.html b/accessible/tests/mochitest/treeupdate/test_bug1189277.html
new file mode 100644
index 000000000..9c995d49e
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_bug1189277.html
@@ -0,0 +1,86 @@
+<html>
+
+<head>
+ <title>Test hide/show events for HTMLListBulletAccessibles on list restyle</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../name.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function runTest()
+ {
+ this.containerNode = getNode("outerDiv");
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode("child")),
+ new invokerChecker(EVENT_HIDE, getNode("childDoc")),
+ new invokerChecker(EVENT_SHOW, "newChildDoc"),
+ new invokerChecker(EVENT_REORDER, this.containerNode)
+ ];
+
+ this.invoke = function runTest_invoke()
+ {
+ this.containerNode.removeChild(getNode("child"));
+
+ var docContainer = getNode("docContainer");
+ var iframe = document.createElement("iframe");
+ iframe.setAttribute("src", "http://example.com");
+ iframe.setAttribute("id", "newChildDoc");
+
+ docContainer.removeChild(getNode("childDoc"));
+ docContainer.appendChild(iframe);
+ }
+
+ this.getID = function runTest_getID()
+ {
+ return "check show events are not incorrectly coalesced";
+ }
+ }
+
+ //enableLogging("tree");
+ gA11yEventDumpToConsole = true;
+ var gQueue = null;
+ function doTest()
+ {
+ gQueue = new eventQueue();
+ gQueue.push(new runTest());
+ gQueue.invoke(); // SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1189277"
+ title="content process crash caused by missing show event">
+ Mozilla Bug 1189277
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="outerDiv">
+ <div id="child">foo</div>
+ <div id="docContainer">
+ <iframe id="childDoc" src="about:blank">
+ </iframe>
+ </div>
+ </div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_bug1276857.html b/accessible/tests/mochitest/treeupdate/test_bug1276857.html
new file mode 100644
index 000000000..5eceae9eb
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_bug1276857.html
@@ -0,0 +1,143 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>DOM mutations test</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function runTest()
+ {
+ // children change will recreate the table
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, getNode('c1'))
+ ];
+
+ this.invoke = function runTest_invoke() {
+ var tree = {
+ SECTION: [ // c1
+ { TEXT_LEAF: [] }, // Some text
+ { TEXT_CONTAINER: [
+ { TEXT_LEAF: [] } // something with ..
+ ] },
+ { TEXT_LEAF: [] } // More text
+ ]
+ };
+ testAccessibleTree('c1', tree);
+
+ getNode('c1_t').querySelector('span').remove();
+ };
+
+ this.finalCheck = function runTest_finalCheck() {
+ var tree = {
+ SECTION: [ // c1
+ { TEXT_LEAF: [] }, // Some text
+ { TEXT_LEAF: [] } // More text
+ ]
+ };
+ testAccessibleTree('c1', tree);
+ };
+
+ this.getID = function runTest_getID()
+ {
+ return 'child DOM node is removed before the layout notifies the a11y about parent removal/show';
+ };
+ }
+
+ function runShadowTest()
+ {
+ // children change will recreate the table
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, 'c2')
+ ];
+
+ this.invoke = function runShadowTest_invoke() {
+ var tree = {
+ SECTION: [ // c2
+ { TEXT_LEAF: [] }, // Some text
+ { TEXT_CONTAINER: [
+ { TEXT_LEAF: [] } // something with ..
+ ] },
+ { TEXT_LEAF: [] } // More text
+ ]
+ };
+ testAccessibleTree('c2', tree);
+
+ gShadowRoot.firstElementChild.querySelector('span').remove();
+ };
+
+ this.finalCheck = function runShadowTest_finalCheck() {
+ var tree = {
+ SECTION: [ // c2
+ { TEXT_LEAF: [] }, // Some text
+ { TEXT_LEAF: [] } // More text
+ ]
+ };
+ testAccessibleTree('c2', tree);
+ };
+
+ this.getID = function runShadowTest_getID() {
+ return 'child DOM node is removed before the layout notifies the a11y about parent removal/show in shadow DOM';
+ };
+ }
+
+ //enableLogging("tree");
+ //gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+ function doTest()
+ {
+ gQueue = new eventQueue();
+ gQueue.push(new runTest());
+ gQueue.push(new runShadowTest());
+ gQueue.invoke(); // will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="c1">
+ <div id="c1_t" style="display: table" role="presentation">
+ Some text
+ <span style="display: table-cell">something with accessibles goes here</span>
+ More text
+ </div>
+ </div>
+
+ <template id="tmpl">
+ <div style="display: table" role="presentation">
+ Some text
+ <span style="display: table-cell">something with accessibles goes here</span>
+ More text
+ </div>
+ </template>
+
+ <div id="c2"><div id="c2_c" role="presentation"></div></div>
+
+ <script>
+ var gShadowRoot = document.getElementById('c2_c').createShadowRoot();
+ var tmpl = document.getElementById('tmpl');
+ gShadowRoot.appendChild(document.importNode(tmpl.content, true));
+ </script>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_bug852150.xhtml b/accessible/tests/mochitest/treeupdate/test_bug852150.xhtml
new file mode 100644
index 000000000..094d148a0
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_bug852150.xhtml
@@ -0,0 +1,59 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>Canvas subdom mutation</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+
+ <script>
+ <![CDATA[
+ function doTest()
+ {
+ var the_displayNone = getNode("the_displaynone");
+ var the_table = getNode("the_table");
+ var the_row = getNode("the_row");
+ ok(isAccessible(the_table), "table should be accessible");
+ the_displayNone.appendChild(the_table);
+ ok(!isAccessible(the_table), "table in display none tree shouldn't be accessible");
+
+ setTimeout(function() {
+ document.body.removeChild(the_row);
+ // make sure no accessibles have stuck around.
+ ok(!isAccessible(the_row), "row shouldn't be accessible");
+ ok(!isAccessible(the_table), "table shouldn't be accessible");
+ ok(!isAccessible(the_displayNone), "display none things shouldn't be accessible");
+ SimpleTest.finish();
+ }, 0);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="test accessible removal when reframe root isn't accessible"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=852150">
+ Mozilla Bug 852150
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="the_displaynone" style="display: none;"></div>
+ <table id="the_table"></table>
+ <tr id="the_row"></tr>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_bug883708.xhtml b/accessible/tests/mochitest/treeupdate/test_bug883708.xhtml
new file mode 100644
index 000000000..6265a1c7f
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_bug883708.xhtml
@@ -0,0 +1,33 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script>
+
+function boom()
+{
+ var newSpan = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ c.insertBefore(newSpan, d);
+ a.style.visibility = "visible";
+ ok(true, "test didn't crash or assert");
+ SimpleTest.finish();
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+ <a target="_blank"
+ title="test reparenting accessible subtree when inaccessible element becomes accessible"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=883708">
+ Mozilla Bug 883708
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+<div style="visibility: collapse;" id="a"><div style="float: right; visibility: visible;"><div id="c"><td id="d"></td></div></div></div></body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_bug884251.xhtml b/accessible/tests/mochitest/treeupdate/test_bug884251.xhtml
new file mode 100644
index 000000000..d1821188b
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_bug884251.xhtml
@@ -0,0 +1,21 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<script>
+
+function boom()
+{
+ document.getElementById("k").removeAttribute("href");
+ ok(true, "changing iframe contents doesn't cause assertions");
+ SimpleTest.finish();
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+<iframe src="data:text/html,1"><link id="k" href="data:text/html,2" /></iframe>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_bug895082.html b/accessible/tests/mochitest/treeupdate/test_bug895082.html
new file mode 100644
index 000000000..aaefdc46c
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_bug895082.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Replace body test</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+function doTest()
+{
+ var y = document.getElementById("y");
+ var oldBody = document.body;
+ var newBody = document.createElement("body")
+ document.documentElement.insertBefore(newBody, oldBody);
+ setTimeout(function() {
+ document.documentElement.removeChild(oldBody);
+ newBody.appendChild(y);
+ ok(true, "we didn't assert");
+ SimpleTest.finish();
+ }, 0);
+}
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=895082"
+ title="Bug 895082 - replacing body element asserts">
+ Bug 895082</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+<div><div id="y"></div></div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_canvas.html b/accessible/tests/mochitest/treeupdate/test_canvas.html
new file mode 100644
index 000000000..6ae129a67
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_canvas.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Canvas subdom mutation</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function addSubtree(aID)
+ {
+ this.node = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, this.node)
+ ];
+
+ this.invoke = function addSubtree_invoke()
+ {
+ // ensure we start with no subtree
+ testAccessibleTree("canvas", { CANVAS: [] });
+ getNode("dialog").style.display = "block";
+ }
+
+ this.finalCheck = function addSubtree_finalCheck() {
+ testAccessibleTree("dialog", { DIALOG: [] });
+ }
+
+ this.getID = function addSubtree_getID()
+ {
+ return "show canvas subdom";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ // make the subdom come alive!
+ gQueue.push(new addSubtree("dialog"));
+
+ gQueue.invoke(); // SimpleTest.finish() will be called in the end
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Expose content in Canvas element"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=495912">
+ Mozilla Bug 495912
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <canvas id="canvas">
+ <div id="dialog" role="dialog" style="display: none;">
+ </div>
+ </canvas>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_colorpicker.xul b/accessible/tests/mochitest/treeupdate/test_colorpicker.xul
new file mode 100644
index 000000000..9b15c9174
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_colorpicker.xul
@@ -0,0 +1,150 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL button hierarchy tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function openColorpicker(aColorpickerID)
+ {
+ this.node = getNode(aColorpickerID);
+
+ this.eventSeq = [];
+
+ var it = new colorButtonIterator(this.node);
+ for (var btnNode = it.next(); btnNode = it.next(); btnNode)
+ this.eventSeq.push(new invokerChecker(EVENT_SHOW, btnNode));
+
+ var popupNode = getPopupNode(this.node);
+ this.eventSeq.push(new invokerChecker(EVENT_REORDER, popupNode));
+
+ this.invoke = function openColorpicker_invoke()
+ {
+ var tree =
+ { BUTTONDROPDOWNGRID: [
+ { ALERT: [ ] }
+ ] };
+ testAccessibleTree(this.node, tree);
+
+ this.node.showPopup();
+ }
+
+ this.finalCheck = function openColorpicker_finalCheck()
+ {
+ var tree =
+ { BUTTONDROPDOWNGRID: [
+ { ALERT: [ ] }
+ ] };
+
+ var colorButtons = tree.BUTTONDROPDOWNGRID[0].ALERT;
+ var it = new colorButtonIterator(this.node);
+ while (it.next()) {
+ var obj = { PUSHBUTTON: [ ] };
+ colorButtons.push(obj);
+ }
+
+ testAccessibleTree(this.node, tree);
+ }
+
+ this.getID = function openColorpicker_getID()
+ {
+ return "open colorpicker " + prettyName(aColorpickerID);
+ }
+ }
+
+ function getPopupNode(aColorpickerNode)
+ {
+ return aColorpickerNode.mPicker.parentNode;
+ }
+
+ function colorButtonIterator(aColorpickerNode)
+ {
+ this.container = aColorpickerNode.mPicker.mBox;
+ this.group = this.container.firstChild;
+ this.item = null;
+
+ this.next = function colorButtonIterator_next()
+ {
+ if (!this.group)
+ return null;
+
+ if (!this.item) {
+ this.item = this.group.firstChild;
+ return this.item;
+ }
+
+ if (this.item.nextSibling) {
+ this.item = this.item.nextSibling;
+ return this.item;
+ }
+
+ if (this.group.nextSibling) {
+ this.group = this.group.nextSibling;
+ this.item = this.group.firstChild;
+ return this.item;
+ }
+
+ this.group = null;
+ this.item = null;
+ return null;
+ }
+ }
+
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTest()
+ {
+ gQueue = new eventQueue();
+ gQueue.push(new openColorpicker("colorpicker"));
+ gQueue.invoke(); // Will call SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=249292"
+ title="Ensure accessible children for toolbarbutton types 'menu' and 'menu-button'">
+ Mozilla Bug 249292
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=630486"
+ title="Don't force accessible creation for popup children.">
+ Mozilla Bug 630486
+ </a>
+ <br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <colorpicker id="colorpicker" type="button"/>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/treeupdate/test_contextmenu.xul b/accessible/tests/mochitest/treeupdate/test_contextmenu.xul
new file mode 100644
index 000000000..5b31e0136
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_contextmenu.xul
@@ -0,0 +1,317 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="menu tree and events">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ function openMenu(aID, aTree)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_MENUPOPUP_START, getNode(aID))
+ ];
+
+ this.invoke = function openMenu_invoke()
+ {
+ var button = getNode("button");
+ getNode(aID).openPopup(button, "after_start", 0, 0, true, false);
+ }
+
+ this.finalCheck = function openMenu_finalCheck(aEvent)
+ {
+ testAccessibleTree(aID, aTree);
+ }
+
+ this.getID = function openMenu_getID()
+ {
+ return "open menu " + prettyName(aID);
+ }
+ }
+
+ function selectNextMenuItem(aID)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, getNode(aID))
+ ];
+
+ this.invoke = function selectMenuItem_invoke()
+ {
+ synthesizeKey("VK_DOWN", { });
+ }
+
+ this.getID = function selectMenuItem_getID()
+ {
+ return "select menuitem " + prettyName(aID);
+ }
+ }
+
+ function openSubMenu(aSubMenuID, aItemID, aMenuID, aTree)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, getNode(aItemID)),
+ ];
+
+ this.invoke = function openSubMenu_invoke()
+ {
+ synthesizeKey("VK_RETURN", { });
+ }
+
+ this.finalCheck = function openSubMenu_finalCheck(aEvent)
+ {
+ testAccessibleTree(aMenuID, aTree);
+ }
+
+ this.getID = function openSubMenu_getID()
+ {
+ return "open submenu " + prettyName(aSubMenuID) + " focusing item " + prettyName(aItemID);
+ }
+ }
+
+ function closeSubMenu(aSubMenuID, aItemID)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, getNode(aItemID)),
+ ];
+
+ this.invoke = function closeSubMenu_invoke()
+ {
+ synthesizeKey("VK_ESCAPE", { });
+ }
+
+ this.getID = function closeSubMenu_getID()
+ {
+ return "close submenu " + prettyName(aSubMenuID) + " focusing item " + prettyName(aItemID);
+ }
+ }
+
+ function closeMenu(aID)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_MENUPOPUP_END, getNode(aID))
+ ];
+
+ this.invoke = function closeMenu_invoke()
+ {
+ synthesizeKey("VK_ESCAPE", { });
+ }
+
+ this.getID = function closeMenu_getID()
+ {
+ return "close menu " + prettyName(aID);
+ }
+ }
+
+ //gA11yEventDumpToConsole = true;
+ //enableLogging("tree,verbose");
+
+ var gQueue = null;
+ var gContextTree = {};
+
+ // Linux and Windows menu trees discrepancy: bug 527646.
+
+ /**
+ * Return the context menu tree before submenus were open.
+ */
+ function getMenuTree1()
+ {
+ if (LINUX || SOLARIS) {
+ var tree = {
+ role: ROLE_MENUPOPUP,
+ children: [
+ {
+ name: "item0",
+ role: ROLE_MENUITEM,
+ children: []
+ },
+ {
+ name: "item1",
+ role: ROLE_MENUITEM,
+ children: []
+ },
+ {
+ name: "item2",
+ role: ROLE_PARENT_MENUITEM,
+ children: [ ]
+ }
+ ]
+ };
+ return tree;
+ }
+
+ // Windows
+ var tree = {
+ role: ROLE_MENUPOPUP,
+ children: [
+ {
+ name: "item0",
+ role: ROLE_MENUITEM,
+ children: []
+ },
+ {
+ name: "item1",
+ role: ROLE_MENUITEM,
+ children: []
+ },
+ {
+ name: "item2",
+ role: ROLE_PARENT_MENUITEM,
+ children: [
+ {
+ name: "item2",
+ role: ROLE_MENUPOPUP,
+ children: [ ]
+ }
+ ]
+ }
+ ]
+ };
+ return tree;
+ }
+
+ /**
+ * Return context menu tree when submenu was open.
+ */
+ function getMenuTree2()
+ {
+ var tree = getMenuTree1();
+ if (LINUX || SOLARIS) {
+ var submenuTree =
+ {
+ name: "item2.0",
+ role: ROLE_PARENT_MENUITEM,
+ children: [ ]
+ };
+ tree.children[2].children.push(submenuTree);
+ return tree;
+ }
+
+ // Windows
+ var submenuTree =
+ {
+ name: "item2.0",
+ role: ROLE_PARENT_MENUITEM,
+ children: [
+ {
+ name: "item2.0",
+ role: ROLE_MENUPOPUP,
+ children: [ ]
+ }
+ ]
+ };
+
+ tree.children[2].children[0].children.push(submenuTree);
+ return tree;
+ }
+
+ /**
+ * Return context menu tree when subsub menu was open.
+ */
+ function getMenuTree3()
+ {
+ var tree = getMenuTree2();
+ var subsubmenuTree =
+ {
+ name: "item2.0.0",
+ role: ROLE_MENUITEM,
+ children: []
+ };
+
+ if (LINUX || SOLARIS)
+ tree.children[2].children[0].children.push(subsubmenuTree);
+ else
+ tree.children[2].children[0].children[0].children[0].children.push(subsubmenuTree);
+
+ return tree;
+ }
+
+
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ // Check initial empty tree
+ testAccessibleTree("context", { MENUPOPUP: [] });
+
+ // Open context menu and check that menu item accesibles are created.
+ gQueue.push(new openMenu("context", getMenuTree1()));
+
+ // Select items and check focus event on them.
+ gQueue.push(new selectNextMenuItem("item0"));
+ gQueue.push(new selectNextMenuItem("item1"));
+ gQueue.push(new selectNextMenuItem("item2"));
+
+ // Open sub menu and check menu accessible tree and focus event.
+ gQueue.push(new openSubMenu("submenu2", "item2.0",
+ "context", getMenuTree2()));
+ gQueue.push(new openSubMenu("submenu2.0", "item2.0.0",
+ "context", getMenuTree3()));
+
+ // Close submenus and check that focus goes to parent.
+ gQueue.push(new closeSubMenu("submenu2.0", "item2.0"));
+ gQueue.push(new closeSubMenu("submenu2", "item2"));
+
+ gQueue.push(new closeMenu("context"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=630194"
+ title="Update accessible tree when opening the menu popup">
+ Mozilla Bug 630194
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=630486"
+ title="Don't force accessible creation for popup children.">
+ Mozilla Bug 630486
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+
+ <menupopup id="context">
+ <menuitem id="item0" label="item0"/>
+ <menuitem id="item1" label="item1"/>
+ <menu id="item2" label="item2">
+ <menupopup id="submenu2">
+ <menu id="item2.0" label="item2.0">
+ <menupopup id="submenu2.0">
+ <menuitem id="item2.0.0" label="item2.0.0"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+ </menu>
+ </menupopup>
+
+ <button context="context" id="button">btn</button>
+ </vbox>
+ </hbox>
+</window>
diff --git a/accessible/tests/mochitest/treeupdate/test_cssoverflow.html b/accessible/tests/mochitest/treeupdate/test_cssoverflow.html
new file mode 100644
index 000000000..ed9edd66e
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_cssoverflow.html
@@ -0,0 +1,143 @@
+<html>
+
+<head>
+ <title>Testing HTML scrollable frames (css overflow style)</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+ ////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Change scroll range to not empty size and inserts a child into container
+ * to trigger tree update of the container. Prior to bug 677154 not empty
+ * size resulted to accessible creation for scroll area, container tree
+ * update picked up that accessible unattaching scroll area accessible
+ * subtree.
+ */
+ function changeScrollRange(aContainerID, aScrollAreaID)
+ {
+ this.containerNode = getNode(aContainerID);
+ this.container = getAccessible(this.containerNode);
+ this.scrollAreaNode = getNode(aScrollAreaID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.container)
+ ];
+
+ this.invoke = function changeScrollRange_invoke()
+ {
+ this.scrollAreaNode.style.width = "20px";
+ this.containerNode.appendChild(document.createElement("input"));
+ }
+
+ this.finalCheck = function changeScrollRange_finalCheck()
+ {
+ var accTree =
+ { SECTION: [ // container
+ { SECTION: [ // scroll area
+ { ENTRY: [] } // child content
+ ] },
+ { ENTRY: [] } // inserted input
+ ] };
+ testAccessibleTree(this.container, accTree);
+ }
+
+ this.getID = function changeScrollRange_getID()
+ {
+ return "change scroll range for " + prettyName(aScrollAreaID);
+ }
+ }
+
+ /**
+ * Change scrollbar styles from hidden to auto. That makes us to create an
+ * accessible for scroll area.
+ */
+ function changeScrollbarStyles(aContainerID, aScrollAreaID)
+ {
+ this.container = getAccessible(aContainerID);
+ this.scrollAreaNode = getNode(aScrollAreaID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getAccessible, this.scrollAreaNode),
+ new invokerChecker(EVENT_REORDER, this.container)
+ ];
+
+ this.invoke = function changeScrollbarStyles_invoke()
+ {
+ var accTree =
+ { SECTION: [] };
+ testAccessibleTree(this.container, accTree);
+
+ this.scrollAreaNode.style.overflow = "auto";
+ }
+
+ this.finalCheck = function changeScrollbarStyles_finalCheck()
+ {
+ var accTree =
+ { SECTION: [ // container
+ { SECTION: [] } // scroll area
+ ] };
+ testAccessibleTree(this.container, accTree);
+ }
+
+ this.getID = function changeScrollbarStyles_getID()
+ {
+ return "change scrollbar styles " + prettyName(aScrollAreaID);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Do tests
+ ////////////////////////////////////////////////////////////////////////////
+
+ var gQueue = null;
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new changeScrollRange("container", "scrollarea"));
+ gQueue.push(new changeScrollbarStyles("container2", "scrollarea2"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=677154"
+ title="Detached document accessibility tree">
+ Mozilla Bug 677154</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <div id="eventdump"></div>
+
+ <div id="container"><div id="scrollarea" style="overflow:auto;"><input></div></div>
+ <div id="container2"><div id="scrollarea2" style="overflow:hidden;"></div></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_deck.xul b/accessible/tests/mochitest/treeupdate/test_deck.xul
new file mode 100644
index 000000000..13561abab
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_deck.xul
@@ -0,0 +1,109 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Tree update on XUL deck panel switching">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function switchDeckPanel(aContainerID, aDeckID)
+ {
+ this.panelIndex = 0;
+
+ this.container = getAccessible(aContainerID);
+ this.deckNode = getNode(aDeckID);
+ this.prevPanel = getAccessible(this.deckNode.selectedPanel);
+ this.panelNode = this.deckNode.childNodes[this.panelIndex];
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, this.prevPanel),
+ new invokerChecker(EVENT_SHOW, this.panelNode),
+ new invokerChecker(EVENT_REORDER, this.container)
+ ];
+
+ this.invoke = function switchDeckPanel_invoke()
+ {
+ var tree =
+ { GROUPING: [ // role="group"
+ { GROUPING: [ // groupbox, a selected panel #2
+ { PUSHBUTTON: [ ] } // button
+ ] }
+ ] };
+ testAccessibleTree(this.container, tree);
+
+ this.deckNode.selectedIndex = this.panelIndex;
+ }
+
+ this.finalCheck = function switchDeckPanel_finalCheck()
+ {
+ var tree =
+ { GROUPING: [ // role="group"
+ { LABEL: [ // description, a selected panel #1
+ { TEXT_LEAF: [] } // text leaf, a description value
+ ] }
+ ] };
+ testAccessibleTree(this.container, tree);
+ }
+
+ this.getID = function switchDeckPanel_getID()
+ {
+ return "switch deck panel";
+ }
+ }
+
+ var gQueue = null;
+ function doTest()
+ {
+ gQueue = new eventQueue();
+ gQueue.push(new switchDeckPanel("container", "deck"));
+ gQueue.invoke(); // will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=814836"
+ title=" xul:deck element messes up screen reader">
+ Mozilla Bug 814836
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1" id="container" role="group">
+
+ <deck id="deck" selectedIndex="1">
+ <description>This is the first page</description>
+ <groupbox>
+ <button label="This is the second page"/>
+ </groupbox>
+ </deck>
+
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/treeupdate/test_doc.html b/accessible/tests/mochitest/treeupdate/test_doc.html
new file mode 100644
index 000000000..05896e7b4
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_doc.html
@@ -0,0 +1,466 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Test document root content mutations</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../states.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Helpers
+
+ function getDocNode(aID)
+ {
+ return getNode(aID).contentDocument;
+ }
+ function getDocChildNode(aID)
+ {
+ return getDocNode(aID).body.firstChild;
+ }
+
+ function rootContentReplaced(aID, aTextName, aRootContentRole)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getDocChildNode, aID),
+ new invokerChecker(EVENT_REORDER, getDocNode, aID)
+ ];
+
+ this.finalCheck = function rootContentReplaced_finalCheck()
+ {
+ var tree = {
+ role: aRootContentRole || ROLE_DOCUMENT,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: aTextName
+ }
+ ]
+ };
+ testAccessibleTree(getDocNode(aID), tree);
+ }
+ }
+
+ function rootContentRemoved(aID)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, null),
+ new invokerChecker(EVENT_REORDER, getDocNode, aID)
+ ];
+
+ this.preinvoke = function rootContentRemoved_preinvoke()
+ {
+ // Set up target for hide event before we invoke.
+ var text = getAccessible(getAccessible(getDocNode(aID)).firstChild);
+ this.eventSeq[0].target = text;
+ }
+
+ this.finalCheck = function rootContentRemoved_finalCheck()
+ {
+ var tree = {
+ role: ROLE_DOCUMENT,
+ children: [ ]
+ };
+ testAccessibleTree(getDocNode(aID), tree);
+ }
+ }
+
+ function rootContentInserted(aID, aTextName)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getDocChildNode, aID),
+ new invokerChecker(EVENT_REORDER, getDocNode, aID)
+ ];
+
+ this.finalCheck = function rootContentInserted_finalCheck()
+ {
+ var tree = {
+ role: ROLE_DOCUMENT,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: aTextName
+ }
+ ]
+ };
+ testAccessibleTree(getDocNode(aID), tree);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function writeIFrameDoc(aID)
+ {
+ this.__proto__ = new rootContentReplaced(aID, "hello");
+
+ this.invoke = function writeIFrameDoc_invoke()
+ {
+ var docNode = getDocNode(aID);
+
+ // We can't use open/write/close outside of iframe document because of
+ // security error.
+ var script = docNode.createElement("script");
+ script.textContent = "document.open(); document.write('hello'); document.close();";
+ docNode.body.appendChild(script);
+ }
+
+ this.getID = function writeIFrameDoc_getID()
+ {
+ return "write document";
+ }
+ }
+
+ /**
+ * Replace HTML element.
+ */
+ function replaceIFrameHTMLElm(aID)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getDocChildNode, aID),
+ new invokerChecker(EVENT_REORDER, getDocNode, aID)
+ ];
+
+ this.invoke = function replaceIFrameHTMLElm_invoke()
+ {
+ var docNode = getDocNode(aID);
+ var newHTMLNode = docNode.createElement("html");
+ newHTMLNode.innerHTML = `<body><p>New Wave</p></body`;
+ docNode.replaceChild(newHTMLNode, docNode.documentElement);
+ }
+
+ this.finalCheck = function replaceIFrameHTMLElm_finalCheck()
+ {
+ var tree = {
+ role: ROLE_DOCUMENT,
+ children: [
+ {
+ role: ROLE_PARAGRAPH,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: 'New Wave'
+ }
+ ]
+ }
+ ]
+ };
+ testAccessibleTree(getDocNode(aID), tree);
+ }
+
+ this.getID = function replaceIFrameHTMLElm_getID()
+ {
+ return "replace HTML element";
+ }
+ }
+
+ /**
+ * Replace HTML body on new body having ARIA role.
+ */
+ function replaceIFrameBody(aID)
+ {
+ this.__proto__ = new rootContentReplaced(aID, "New Hello");
+
+ this.invoke = function replaceIFrameBody_invoke()
+ {
+ var docNode = getDocNode(aID);
+ var newBodyNode = docNode.createElement("body");
+ var newTextNode = docNode.createTextNode("New Hello");
+ newBodyNode.appendChild(newTextNode);
+ docNode.documentElement.replaceChild(newBodyNode, docNode.body);
+ }
+
+ this.getID = function replaceIFrameBody_getID()
+ {
+ return "replace body";
+ }
+ }
+
+ /**
+ * Replace HTML body on new body having ARIA role.
+ */
+ function replaceIFrameBodyOnARIARoleBody(aID)
+ {
+ this.__proto__ = new rootContentReplaced(aID, "New Hello",
+ ROLE_PUSHBUTTON);
+
+ this.invoke = function replaceIFrameBodyOnARIARoleBody_invoke()
+ {
+ var docNode = getDocNode(aID);
+ var newBodyNode = docNode.createElement("body");
+ var newTextNode = docNode.createTextNode("New Hello");
+ newBodyNode.appendChild(newTextNode);
+ newBodyNode.setAttribute("role", "button");
+ docNode.documentElement.replaceChild(newBodyNode, docNode.body);
+ }
+
+ this.getID = function replaceIFrameBodyOnARIARoleBody_getID()
+ {
+ return "replace body on body having ARIA role";
+ }
+ }
+
+ /**
+ * Open/close document pair.
+ */
+ function openIFrameDoc(aID)
+ {
+ this.__proto__ = new rootContentRemoved(aID);
+
+ this.invoke = function openIFrameDoc_invoke()
+ {
+ this.preinvoke();
+
+ // Open document.
+ var docNode = getDocNode(aID);
+ var script = docNode.createElement("script");
+ script.textContent = "function closeMe() { document.write('Works?'); document.close(); } window.closeMe = closeMe; document.open();";
+ docNode.body.appendChild(script);
+ }
+
+ this.getID = function openIFrameDoc_getID()
+ {
+ return "open document";
+ }
+ }
+
+ function closeIFrameDoc(aID)
+ {
+ this.__proto__ = new rootContentInserted(aID, "Works?");
+
+ this.invoke = function closeIFrameDoc_invoke()
+ {
+ // Write and close document.
+ getDocNode(aID).write('Works?'); getDocNode(aID).close();
+ }
+
+ this.getID = function closeIFrameDoc_getID()
+ {
+ return "close document";
+ }
+ }
+
+ /**
+ * Remove/insert HTML element pair.
+ */
+ function removeHTMLFromIFrameDoc(aID)
+ {
+ this.__proto__ = new rootContentRemoved(aID);
+
+ this.invoke = function removeHTMLFromIFrameDoc_invoke()
+ {
+ this.preinvoke();
+
+ // Remove HTML element.
+ var docNode = getDocNode(aID);
+ docNode.removeChild(docNode.firstChild);
+ }
+
+ this.getID = function removeHTMLFromIFrameDoc_getID()
+ {
+ return "remove HTML element";
+ }
+ }
+
+ function insertHTMLToIFrameDoc(aID)
+ {
+ this.__proto__ = new rootContentInserted(aID, "Haha");
+
+ this.invoke = function insertHTMLToIFrameDoc_invoke()
+ {
+ // Insert HTML element.
+ var docNode = getDocNode(aID);
+ var html = docNode.createElement("html");
+ var body = docNode.createElement("body");
+ var text = docNode.createTextNode("Haha");
+ body.appendChild(text);
+ html.appendChild(body);
+ docNode.appendChild(html);
+ }
+
+ this.getID = function insertHTMLToIFrameDoc_getID()
+ {
+ return "insert HTML element document";
+ }
+ }
+
+ /**
+ * Remove/insert HTML body pair.
+ */
+ function removeBodyFromIFrameDoc(aID)
+ {
+ this.__proto__ = new rootContentRemoved(aID);
+
+ this.invoke = function removeBodyFromIFrameDoc_invoke()
+ {
+ this.preinvoke();
+
+ // Remove body element.
+ var docNode = getDocNode(aID);
+ docNode.documentElement.removeChild(docNode.body);
+ }
+
+ this.getID = function removeBodyFromIFrameDoc_getID()
+ {
+ return "remove body element";
+ }
+ }
+
+ function insertElmUnderDocElmWhileBodyMissed(aID)
+ {
+ this.docNode = null;
+ this.inputNode = null;
+
+ function getInputNode()
+ { return this.inputNode; }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getInputNode.bind(this)),
+ new invokerChecker(EVENT_REORDER, getDocNode, aID)
+ ];
+
+ this.invoke = function invoke()
+ {
+ this.docNode = getDocNode(aID);
+ this.inputNode = this.docNode.createElement("input");
+ this.docNode.documentElement.appendChild(this.inputNode);
+ }
+
+ this.finalCheck = function finalCheck()
+ {
+ var tree =
+ { DOCUMENT: [
+ { ENTRY: [ ] }
+ ] };
+ testAccessibleTree(this.docNode, tree);
+
+ // Remove aftermath of this test before next test starts.
+ this.docNode.documentElement.removeChild(this.inputNode);
+ }
+
+ this.getID = function getID()
+ {
+ return "Insert element under document element while body is missed.";
+ }
+ }
+
+ function insertBodyToIFrameDoc(aID)
+ {
+ this.__proto__ = new rootContentInserted(aID, "Yo ho ho i butylka roma!");
+
+ this.invoke = function insertBodyToIFrameDoc_invoke()
+ {
+ // Insert body element.
+ var docNode = getDocNode(aID);
+ var body = docNode.createElement("body");
+ var text = docNode.createTextNode("Yo ho ho i butylka roma!");
+ body.appendChild(text);
+ docNode.documentElement.appendChild(body);
+ }
+
+ this.getID = function insertBodyToIFrameDoc_getID()
+ {
+ return "insert body element";
+ }
+ }
+
+ function changeSrc(aID)
+ {
+ this.containerNode = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.containerNode)
+ ];
+
+ this.invoke = function changeSrc_invoke()
+ {
+ this.containerNode.src = "data:text/html,<html><input></html>";
+ }
+
+ this.finalCheck = function changeSrc_finalCheck()
+ {
+ var tree =
+ { INTERNAL_FRAME: [
+ { DOCUMENT: [
+ { ENTRY: [ ] }
+ ] }
+ ] };
+ testAccessibleTree(this.containerNode, tree);
+ }
+
+ this.getID = function changeSrc_getID()
+ {
+ return "change src on iframe";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ //gA11yEventDumpToConsole = true;
+ //enableLogging('tree,verbose');
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new writeIFrameDoc("iframe"));
+ gQueue.push(new replaceIFrameHTMLElm("iframe"));
+ gQueue.push(new replaceIFrameBody("iframe"));
+ gQueue.push(new openIFrameDoc("iframe"));
+ gQueue.push(new closeIFrameDoc("iframe"));
+ gQueue.push(new removeHTMLFromIFrameDoc("iframe"));
+ gQueue.push(new insertHTMLToIFrameDoc("iframe"));
+ gQueue.push(new removeBodyFromIFrameDoc("iframe"));
+ gQueue.push(new insertElmUnderDocElmWhileBodyMissed("iframe"));
+ gQueue.push(new insertBodyToIFrameDoc("iframe"));
+ gQueue.push(new changeSrc("iframe"));
+ gQueue.push(new replaceIFrameBodyOnARIARoleBody("iframe"));
+
+ gQueue.invoke(); // SimpleTest.finish() will be called in the end
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Update accessible tree when root element is changed"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=606082">Mozilla Bug 606082</a>
+ <a target="_blank"
+ title="Elements inserted outside the body aren't accessible"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=608887">Mozilla Bug 608887</a>
+ <a target="_blank"
+ title="Reorder event for document must be fired after document initial tree creation"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=669263">Mozilla Bug 669263</a>
+ <a target="_blank"
+ title="Changing the HTML body doesn't pick up ARIA role"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=818407">Mozilla Bug 818407</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <iframe id="iframe"></iframe>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_gencontent.html b/accessible/tests/mochitest/treeupdate/test_gencontent.html
new file mode 100644
index 000000000..1cf22b36f
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_gencontent.html
@@ -0,0 +1,160 @@
+<html>
+
+<head>
+ <title>Elements with CSS generated content</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <style>
+ .gentext:before {
+ content: "START"
+ }
+ .gentext:after {
+ content: "END"
+ }
+ </style>
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+ ////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Insert new node with CSS generated content style applied to container.
+ */
+ function insertNodeHavingGenContent(aContainerID)
+ {
+ this.containerNode = getNode(aContainerID);
+ this.container = getAccessible(this.containerNode);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getFirstChild, this.container),
+ new invokerChecker(EVENT_REORDER, this.container)
+ ];
+
+ this.invoke = function insertNodeHavingGenContent_invoke()
+ {
+ var node = document.createElement("div");
+ node.textContent = "text";
+ node.setAttribute("class", "gentext");
+ this.containerNode.appendChild(node);
+ }
+
+ this.finalCheck = function insertNodeHavingGenContent_finalCheck()
+ {
+ var accTree =
+ { SECTION: [ // container
+ { SECTION: [ // inserted node
+ { STATICTEXT: [] }, // :before
+ { TEXT_LEAF: [] }, // primary text
+ { STATICTEXT: [] } // :after
+ ] }
+ ] };
+ testAccessibleTree(this.container, accTree);
+ }
+
+ this.getID = function insertNodeHavingGenContent_getID()
+ {
+ return "insert node having generated content to " + prettyName(aContainerID);
+ }
+ }
+
+ /**
+ * Add CSS generated content to the given node contained by container node.
+ */
+ function addGenContent(aContainerID, aNodeID)
+ {
+ this.container = getAccessible(aContainerID);
+ this.node = getNode(aNodeID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, this.container.firstChild),
+ new invokerChecker(EVENT_SHOW, getFirstChild, this.container),
+ new invokerChecker(EVENT_REORDER, this.container)
+ ];
+
+ this.invoke = function addGenContent_invoke()
+ {
+ this.node.setAttribute("class", "gentext");
+ }
+
+ this.finalCheck = function insertNodeHavingGenContent_finalCheck()
+ {
+ var accTree =
+ { SECTION: [ // container
+ { SECTION: [ // inserted node
+ { STATICTEXT: [] }, // :before
+ { TEXT_LEAF: [] }, // primary text
+ { STATICTEXT: [] } // :after
+ ] }
+ ] };
+ testAccessibleTree(this.container, accTree);
+ }
+
+ this.getID = function addGenContent_getID()
+ {
+ return "add generated content to" + prettyName(aNodeID);
+ }
+ }
+
+ /**
+ * Target getters.
+ */
+ function getFirstChild(aAcc)
+ {
+ try { return aAcc.getChildAt(0); }
+ catch (e) { return null; }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Do tests
+ ////////////////////////////////////////////////////////////////////////////
+
+ var gQueue = null;
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new insertNodeHavingGenContent("container1"));
+ gQueue.push(new addGenContent("container2", "container2_child"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=646350"
+ title="Add a test for dynamic chnages of CSS generated content">
+ Mozilla Bug 646350</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+ <div id="eventdump"></div>
+
+ <div id="container1"></div>
+ <div id="container2"><div id="container2_child">text</div></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_general.html b/accessible/tests/mochitest/treeupdate/test_general.html
new file mode 100644
index 000000000..9a8862bea
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_general.html
@@ -0,0 +1,150 @@
+<html>
+
+<head>
+ <title>Testing the tree updates</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+ ////////////////////////////////////////////////////////////////////////////
+
+ function prependAppend(aContainer)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, aContainer)
+ ];
+
+ this.invoke = function prependAppend_invoke()
+ {
+ var checkbox = document.createElement('input');
+ checkbox.setAttribute('type', 'checkbox');
+ getNode(aContainer).insertBefore(checkbox, getNode(aContainer).firstChild);
+
+ var button = document.createElement('input');
+ button.setAttribute('type', 'button');
+ getNode(aContainer).appendChild(button);
+ }
+
+ this.finalCheck = function prependAppend_finalCheck()
+ {
+ var accTree =
+ { SECTION: [ // container
+ { CHECKBUTTON: [ ] },
+ { ENTRY: [ ] },
+ { PUSHBUTTON: [ ] }
+ ] };
+ testAccessibleTree(aContainer, accTree);
+ }
+
+ this.getID = function prependAppend_getID()
+ {
+ return "prepends a child and appends a child";
+ }
+ }
+
+ function removeRemove(aContainer)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, aContainer)
+ ];
+
+ this.invoke = function removeRemove_invoke()
+ {
+ getNode(aContainer).removeChild(getNode(aContainer).firstChild);
+ }
+
+ this.finalCheck = function removeRemove_finalCheck()
+ {
+ var accTree =
+ { SECTION: [ // container
+ { PUSHBUTTON: [ ] }
+ ] };
+ testAccessibleTree(aContainer, accTree);
+ }
+
+ this.getID = function removeRemove_getID()
+ {
+ return "remove first and second children";
+ }
+ }
+
+ function insertInaccessibleAccessibleSiblings()
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, "c3")
+ ];
+
+ this.invoke = function insertInaccessibleAccessibleSiblings_invoke()
+ {
+ getNode("c3").appendChild(document.createElement("span"));
+ getNode("c3").appendChild(document.createElement("input"));
+ }
+
+ this.finalCheck = function insertInaccessibleAccessibleSiblings_finalCheck()
+ {
+ var accTree =
+ { SECTION: [ // container
+ { PUSHBUTTON: [
+ { TEXT_LEAF: [] }
+ ] },
+ { ENTRY: [ ] }
+ ] };
+ testAccessibleTree("c3", accTree);
+ }
+
+ this.getID = function insertInaccessibleAccessibleSiblings_getID()
+ {
+ return "insert inaccessible and then accessible siblings";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Do tests
+ ////////////////////////////////////////////////////////////////////////////
+
+ var gQueue = null;
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ function doTests()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new prependAppend("c1"));
+ gQueue.push(new removeRemove("c2"));
+ gQueue.push(new insertInaccessibleAccessibleSiblings());
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTests);
+ </script>
+</head>
+
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="c1"><input></div>
+ <div id="c2"><span><input type="checkbox"><input></span><input type="button"></div>
+
+ <div id="c3"><input type="button" value="button"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_hidden.html b/accessible/tests/mochitest/treeupdate/test_hidden.html
new file mode 100644
index 000000000..2adb9efeb
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_hidden.html
@@ -0,0 +1,135 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>@hidden attribute testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+ ////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Set @hidden attribute
+ */
+ function setHiddenAttr(aContainerID, aChildID)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, getNode(aContainerID))
+ ];
+
+ this.invoke = function setHiddenAttr_invoke()
+ {
+ var tree =
+ { SECTION: [
+ { ENTRY: [
+ ] }
+ ] };
+ testAccessibleTree(aContainerID, tree);
+
+ getNode(aChildID).setAttribute("hidden", "true");
+ }
+
+ this.finalCheck = function setHiddenAttr_finalCheck()
+ {
+ var tree =
+ { SECTION: [
+ ] };
+ testAccessibleTree(aContainerID, tree);
+ }
+
+ this.getID = function setHiddenAttr_getID()
+ {
+ return "Set @hidden attribute on input and test accessible tree for div";
+ }
+ }
+
+ /**
+ * Remove @hidden attribute
+ */
+ function removeHiddenAttr(aContainerID, aChildID)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, getNode(aContainerID))
+ ];
+
+ this.invoke = function removeHiddenAttr_invoke()
+ {
+ var tree =
+ { SECTION: [
+ ] };
+ testAccessibleTree(aContainerID, tree);
+
+ getNode(aChildID).removeAttribute("hidden");
+ }
+
+ this.finalCheck = function removeHiddenAttr_finalCheck()
+ {
+ var tree =
+ { SECTION: [
+ { ENTRY: [
+ ] }
+ ] };
+ testAccessibleTree(aContainerID, tree);
+ }
+
+ this.getID = function removeHiddenAttr_getID()
+ {
+ return "Remove @hidden attribute on input and test accessible tree for div";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+ ////////////////////////////////////////////////////////////////////////////
+
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new setHiddenAttr("container", "child"));
+ gQueue.push(new removeHiddenAttr("container", "child"));
+
+ gQueue.invoke(); // SimpleTest.finish() will be called in the end
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+
+ </script>
+
+</head>
+
+<body>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="container"><input id="child"></div>
+
+ <div id="eventdump"></div>
+
+</body>
+
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_imagemap.html b/accessible/tests/mochitest/treeupdate/test_imagemap.html
new file mode 100644
index 000000000..c7c06b3d1
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_imagemap.html
@@ -0,0 +1,442 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>HTML img map accessible tree update tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+ function insertArea(aImageMapID, aMapID)
+ {
+ this.imageMap = getAccessible(aImageMapID);
+ this.mapNode = getNode(aMapID);
+
+ function getInsertedArea(aThisObj)
+ {
+ return aThisObj.imageMap.firstChild;
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getInsertedArea, this),
+ new invokerChecker(EVENT_REORDER, this.imageMap)
+ ];
+
+ this.invoke = function insertArea_invoke()
+ {
+ var areaElm = document.createElement("area");
+ areaElm.setAttribute("href",
+ "http://www.bbc.co.uk/radio4/atoz/index.shtml#a");
+ areaElm.setAttribute("coords", "0,0,13,14");
+ areaElm.setAttribute("alt", "a");
+ areaElm.setAttribute("shape", "rect");
+
+ this.mapNode.insertBefore(areaElm, this.mapNode.firstChild);
+ }
+
+ this.finalCheck = function insertArea_finalCheck()
+ {
+ var accTree =
+ { IMAGE_MAP: [
+ {
+ role: ROLE_LINK,
+ name: "a",
+ children: [ ]
+ },
+ {
+ role: ROLE_LINK,
+ name: "b",
+ children: [ ]
+ },
+ ] };
+ testAccessibleTree(this.imageMap, accTree);
+ }
+
+ this.getID = function insertArea_getID()
+ {
+ return "insert area element";
+ }
+ }
+
+ function appendArea(aImageMapID, aMapID)
+ {
+ this.imageMap = getAccessible(aImageMapID);
+ this.mapNode = getNode(aMapID);
+
+ function getAppendedArea(aThisObj)
+ {
+ return aThisObj.imageMap.lastChild;
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getAppendedArea, this),
+ new invokerChecker(EVENT_REORDER, this.imageMap)
+ ];
+
+ this.invoke = function appendArea_invoke()
+ {
+ var areaElm = document.createElement("area");
+ areaElm.setAttribute("href",
+ "http://www.bbc.co.uk/radio4/atoz/index.shtml#c");
+ areaElm.setAttribute("coords", "34,0,47,14");
+ areaElm.setAttribute("alt", "c");
+ areaElm.setAttribute("shape", "rect");
+
+ this.mapNode.appendChild(areaElm);
+ }
+
+ this.finalCheck = function appendArea_finalCheck()
+ {
+ var accTree =
+ { IMAGE_MAP: [
+ {
+ role: ROLE_LINK,
+ name: "a",
+ children: [ ]
+ },
+ {
+ role: ROLE_LINK,
+ name: "b",
+ children: [ ]
+ },
+ {
+ role: ROLE_LINK,
+ name: "c",
+ children: [ ]
+ }
+ ] };
+ testAccessibleTree(this.imageMap, accTree);
+ }
+
+ this.getID = function appendArea_getID()
+ {
+ return "append area element";
+ }
+ }
+
+ function removeArea(aImageMapID, aMapID)
+ {
+ this.imageMap = getAccessible(aImageMapID);
+ this.area = null;
+ this.mapNode = getNode(aMapID);
+
+ function getRemovedArea(aThisObj)
+ {
+ return aThisObj.area;
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getRemovedArea, this),
+ new invokerChecker(EVENT_REORDER, this.imageMap)
+ ];
+
+ this.invoke = function removeArea_invoke()
+ {
+ this.area = this.imageMap.firstChild;
+ this.mapNode.removeChild(this.mapNode.firstElementChild);
+ }
+
+ this.finalCheck = function removeArea_finalCheck()
+ {
+ var accTree =
+ { IMAGE_MAP: [
+ {
+ role: ROLE_LINK,
+ name: "b",
+ children: [ ]
+ },
+ {
+ role: ROLE_LINK,
+ name: "c",
+ children: [ ]
+ }
+ ] };
+ testAccessibleTree(this.imageMap, accTree);
+ }
+
+ this.getID = function removeArea_getID()
+ {
+ return "remove area element";
+ }
+ }
+
+ function removeNameOnMap(aImageMapContainerID, aImageMapID, aMapID)
+ {
+ this.container = getAccessible(aImageMapContainerID);
+ this.containerNode = this.container.DOMNode;
+ this.imageMap = getAccessible(aImageMapID);
+ this.imgNode = this.imageMap.DOMNode;
+ this.mapNode = getNode(aMapID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, this.imageMap),
+ new invokerChecker(EVENT_SHOW, this.imgNode),
+ new invokerChecker(EVENT_REORDER, this.container)
+ ];
+
+ this.invoke = function removeNameOnMap_invoke()
+ {
+ this.mapNode.removeAttribute("name");
+ }
+
+ this.finalCheck = function removeNameOnMap_finalCheck()
+ {
+ var accTree =
+ { SECTION: [
+ { GRAPHIC: [ ] }
+ ] };
+ testAccessibleTree(this.container, accTree);
+ }
+
+ this.getID = function removeNameOnMap_getID()
+ {
+ return "remove @name on map element";
+ }
+ }
+
+ function restoreNameOnMap(aImageMapContainerID, aImageMapID, aMapID)
+ {
+ this.container = getAccessible(aImageMapContainerID);
+ this.containerNode = this.container.DOMNode;
+ this.imageMap = null;
+ this.imgNode = getNode(aImageMapID);
+ this.mapNode = getNode(aMapID);
+
+ function getImageMap(aThisObj)
+ {
+ return aThisObj.imageMap;
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getImageMap, this),
+ new invokerChecker(EVENT_SHOW, this.imgNode),
+ new invokerChecker(EVENT_REORDER, this.container)
+ ];
+
+ this.invoke = function restoreNameOnMap_invoke()
+ {
+ this.imageMap = getAccessible(aImageMapID);
+ this.mapNode.setAttribute("name", "atoz_map");
+
+ // XXXhack: force repainting of the image (see bug 745788 for details).
+ waveOverImageMap(aImageMapID);
+ }
+
+ this.finalCheck = function removeNameOnMap_finalCheck()
+ {
+ var accTree =
+ { SECTION: [
+ { IMAGE_MAP: [
+ { LINK: [ ] },
+ { LINK: [ ] }
+ ] }
+ ] };
+ testAccessibleTree(this.container, accTree);
+ }
+
+ this.getID = function removeNameOnMap_getID()
+ {
+ return "restore @name on map element";
+ }
+ }
+
+ function removeMap(aImageMapContainerID, aImageMapID, aMapID)
+ {
+ this.container = getAccessible(aImageMapContainerID);
+ this.containerNode = this.container.DOMNode;
+ this.imageMap = null;
+ this.imgNode = getNode(aImageMapID);
+ this.mapNode = getNode(aMapID);
+
+ function getImageMap(aThisObj)
+ {
+ return aThisObj.imageMap;
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getImageMap, this),
+ new invokerChecker(EVENT_SHOW, this.imgNode),
+ new invokerChecker(EVENT_REORDER, this.container)
+ ];
+
+ this.invoke = function removeMap_invoke()
+ {
+ this.imageMap = getAccessible(aImageMapID);
+ this.mapNode.parentNode.removeChild(this.mapNode);
+ }
+
+ this.finalCheck = function removeMap_finalCheck()
+ {
+ var accTree =
+ { SECTION: [
+ { GRAPHIC: [ ] }
+ ] };
+ testAccessibleTree(this.container, accTree);
+ }
+
+ this.getID = function removeMap_getID()
+ {
+ return "remove map element";
+ }
+ }
+
+ function insertMap(aImageMapContainerID, aImageID)
+ {
+ this.container = getAccessible(aImageMapContainerID);
+ this.containerNode = this.container.DOMNode;
+ this.image = null;
+ this.imgMapNode = getNode(aImageID);
+
+ function getImage(aThisObj)
+ {
+ return aThisObj.image;
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getImage, this),
+ new invokerChecker(EVENT_SHOW, this.imgMapNode),
+ new invokerChecker(EVENT_REORDER, this.container)
+ ];
+
+ this.invoke = function insertMap_invoke()
+ {
+ this.image = getAccessible(aImageID);
+
+ var map = document.createElement("map");
+ map.setAttribute("name", "atoz_map");
+ map.setAttribute("id", "map");
+
+ var area = document.createElement("area")
+ area.setAttribute("href",
+ "http://www.bbc.co.uk/radio4/atoz/index.shtml#b");
+ area.setAttribute("coords", "17,0,30,14");
+ area.setAttribute("alt", "b");
+ area.setAttribute("shape", "rect");
+
+ map.appendChild(area);
+
+ this.containerNode.appendChild(map);
+ }
+
+ this.finalCheck = function insertMap_finalCheck()
+ {
+ var accTree =
+ { SECTION: [
+ { IMAGE_MAP: [
+ { LINK: [ ] }
+ ] }
+ ] };
+ testAccessibleTree(this.container, accTree);
+ }
+
+ this.getID = function insertMap_getID()
+ {
+ return "insert map element";
+ }
+ }
+
+ function hideImageMap(aContainerID, aImageID)
+ {
+ this.container = getAccessible(aContainerID);
+ this.imageMap = null;
+ this.imageMapNode = getNode(aImageID);
+
+ function getImageMap(aThisObj)
+ {
+ return aThisObj.imageMap;
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getImageMap, this),
+ new invokerChecker(EVENT_REORDER, aContainerID)
+ ];
+
+ this.invoke = function hideImageMap_invoke()
+ {
+ this.imageMap = getAccessible(this.imageMapNode);
+ this.imageMapNode.style.display = "none";
+ }
+
+ this.finalCheck = function hideImageMap_finalCheck()
+ {
+ var accTree =
+ { SECTION: [ ] };
+ testAccessibleTree(this.container, accTree);
+ }
+
+ this.getID = function hideImageMap_getID()
+ {
+ return "display:none image";
+ }
+ }
+
+ //gA11yEventDumpToConsole = true; // debug stuff
+ function doPreTest()
+ {
+ waitForImageMap("imgmap", doTest);
+ }
+
+ var gQueue = null;
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new insertArea("imgmap", "map"));
+ gQueue.push(new appendArea("imgmap", "map"));
+ gQueue.push(new removeArea("imgmap", "map"));
+ gQueue.push(new removeNameOnMap("container", "imgmap", "map"));
+ gQueue.push(new restoreNameOnMap("container", "imgmap", "map"));
+ gQueue.push(new removeMap("container", "imgmap", "map"));
+ gQueue.push(new insertMap("container", "imgmap"));
+ gQueue.push(new hideImageMap("container", "imgmap"));
+
+ //enableLogging("tree"); // debug stuff
+ //gQueue.onFinish = function() { disableLogging("tree"); }
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doPreTest);
+ </script>
+
+</head>
+<body>
+
+ <a target="_blank"
+ title="Image map accessible tree is not updated when image map is changed"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=732389">
+ Mozilla Bug 732389
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <map name="atoz_map" id="map">
+ <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b"
+ coords="17,0,30,14" alt="b" shape="rect">
+ </map>
+
+ <div id="container">
+ <img id="imgmap" width="447" height="15"
+ usemap="#atoz_map"
+ src="../letters.gif"><!--
+ Important: no whitespace between the <img> and the </div>, so we
+ don't end up with textframes there, because those would be reflected
+ in our accessible tree in some cases.
+ --></div>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_list.html b/accessible/tests/mochitest/treeupdate/test_list.html
new file mode 100644
index 000000000..9196142d9
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_list.html
@@ -0,0 +1,152 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Test HTML li and listitem bullet accessible cache</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Helpers
+
+ function testLiAccessibleTree()
+ {
+ // Test accessible tree.
+ var accTree = {
+ role: ROLE_LISTITEM,
+ children: [
+ {
+ role: ROLE_STATICTEXT,
+ children: []
+ },
+ {
+ role: ROLE_TEXT_LEAF,
+ children: []
+ }
+ ]
+ };
+
+ testAccessibleTree("li", accTree);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Sequence item processors
+
+ function hideProcessor()
+ {
+ this.liNode = getNode("li");
+ this.li = getAccessible(this.liNode);
+ this.bullet = this.li.firstChild;
+
+ this.process = function hideProcessor_process()
+ {
+ this.liNode.style.display = "none";
+ }
+
+ this.onProcessed = function hideProcessor_onProcessed()
+ {
+ window.setTimeout(
+ function(aLiAcc, aLiNode, aBulletAcc)
+ {
+ testDefunctAccessible(aLiAcc, aLiNode);
+ testDefunctAccessible(aBulletAcc);
+
+ gSequence.processNext();
+ },
+ 0, this.li, this.liNode, this.bullet
+ );
+ }
+ };
+
+ function showProcessor()
+ {
+ this.liNode = getNode("li");
+
+ this.process = function showProcessor_process()
+ {
+ this.liNode.style.display = "list-item";
+ }
+
+ this.onProcessed = function showProcessor_onProcessed()
+ {
+ testLiAccessibleTree();
+ gSequence.processNext();
+ }
+ };
+
+ function textReplaceProcessor()
+ {
+ this.liNode = getNode("li");
+
+ this.process = function textReplaceProcessor_process()
+ {
+ this.liNode.textContent = "hey";
+ }
+
+ this.onProcessed = function textReplaceProcessor_onProcessed()
+ {
+ var tree = {
+ LISTITEM: [
+ { STATICTEXT: [] },
+ { TEXT_LEAF: [] }
+ ]
+ };
+ testAccessibleTree(this.liNode, tree);
+ SimpleTest.finish();
+ }
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ //gA11yEventDumpToConsole = true;
+
+ var gSequence = null;
+ function doTest()
+ {
+ testLiAccessibleTree();
+
+ gSequence = new sequence();
+
+ gSequence.append(new hideProcessor(), EVENT_HIDE, getAccessible("li"),
+ "hide HTML li");
+ gSequence.append(new showProcessor(), EVENT_SHOW, getNode("li"),
+ "show HTML li");
+ gSequence.append(new textReplaceProcessor(), EVENT_REORDER, getNode("li"),
+ "change text of HTML li");
+
+ gSequence.processNext(); // SimpleTest.finish() will be called in the end
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="setParent shouldn't be virtual"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=496783">Mozilla Bug 496783</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <ul>
+ <li id="li">item1</li>
+ </ul>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_list_editabledoc.html b/accessible/tests/mochitest/treeupdate/test_list_editabledoc.html
new file mode 100644
index 000000000..d4c178cb9
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_list_editabledoc.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Test HTML li and listitem bullet accessible insertion into editable document</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function addLi(aID)
+ {
+ this.listNode = getNode(aID);
+ this.liNode = document.createElement("li");
+ this.liNode.textContent = "item";
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getAccessible, this.liNode),
+ new invokerChecker(EVENT_REORDER, this.listNode)
+ ];
+
+ this.invoke = function addLi_invoke()
+ {
+ this.listNode.appendChild(this.liNode);
+ }
+
+ this.finalCheck = function addLi_finalCheck()
+ {
+ var tree = {
+ role: ROLE_LIST,
+ children: [
+ {
+ role: ROLE_LISTITEM,
+ children: [
+ {
+ role: ROLE_STATICTEXT,
+ name: "1. ",
+ children: []
+ },
+ {
+ role: ROLE_TEXT_LEAF,
+ children: []
+ }
+ ]
+ }
+ ]
+ };
+ testAccessibleTree(aID, tree);
+ }
+
+ this.getID = function addLi_getID()
+ {
+ return "add li";
+ }
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new addLi("list"));
+
+ gQueue.invoke(); // SimpleTest.finish() will be called in the end
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body contentEditable="true">
+
+ <a target="_blank"
+ title="Wrong list bullet text of accessible for the first numbered HTML:li in CKEditor"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=557795">Mozilla Bug 557795</a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <ol id="list">
+ </ol>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_listbox.xul b/accessible/tests/mochitest/treeupdate/test_listbox.xul
new file mode 100644
index 000000000..862b4dde8
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_listbox.xul
@@ -0,0 +1,180 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL listbox hierarchy tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ function insertListitem(aListboxID)
+ {
+ this.listboxNode = getNode(aListboxID);
+
+ this.listitemNode = document.createElement("listitem");
+ this.listitemNode.setAttribute("label", "item1");
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, this.listitemNode),
+ new invokerChecker(EVENT_REORDER, this.listboxNode)
+ ];
+
+ this.invoke = function insertListitem_invoke()
+ {
+ this.listboxNode.insertBefore(this.listitemNode,
+ this.listboxNode.firstChild);
+ }
+
+ this.finalCheck = function insertListitem_finalCheck()
+ {
+ var tree =
+ { LISTBOX: [
+ {
+ role: ROLE_RICH_OPTION,
+ name: "item1"
+ },
+ {
+ role: ROLE_RICH_OPTION,
+ name: "item2"
+ },
+ {
+ role: ROLE_RICH_OPTION,
+ name: "item3"
+ },
+ {
+ role: ROLE_RICH_OPTION,
+ name: "item4"
+ }
+ ] };
+ testAccessibleTree(this.listboxNode, tree);
+ }
+
+ this.getID = function insertListitem_getID()
+ {
+ return "insert listitem ";
+ }
+ }
+
+ function removeListitem(aListboxID)
+ {
+ this.listboxNode = getNode(aListboxID);
+ this.listitemNode = null;
+ this.listitem;
+
+ function getListitem(aThisObj)
+ {
+ return aThisObj.listitem;
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getListitem, this),
+ new invokerChecker(EVENT_REORDER, this.listboxNode)
+ ];
+
+ this.invoke = function removeListitem_invoke()
+ {
+ this.listitemNode = this.listboxNode.firstChild;
+ this.listitem = getAccessible(this.listitemNode);
+
+ this.listboxNode.removeChild(this.listitemNode);
+ }
+
+ this.finalCheck = function removeListitem_finalCheck()
+ {
+ var tree =
+ { LISTBOX: [
+ {
+ role: ROLE_RICH_OPTION,
+ name: "item2"
+ },
+ {
+ role: ROLE_RICH_OPTION,
+ name: "item3"
+ },
+ {
+ role: ROLE_RICH_OPTION,
+ name: "item4"
+ }
+ ] };
+ testAccessibleTree(this.listboxNode, tree);
+ }
+
+ this.getID = function removeListitem_getID()
+ {
+ return "remove listitem ";
+ }
+ }
+
+ //gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+ function doTest()
+ {
+ var tree =
+ { LISTBOX: [
+ {
+ role: ROLE_RICH_OPTION,
+ name: "item2"
+ },
+ {
+ role: ROLE_RICH_OPTION,
+ name: "item3"
+ },
+ {
+ role: ROLE_RICH_OPTION,
+ name: "item4"
+ }
+ ] };
+ testAccessibleTree("listbox", tree);
+
+ gQueue = new eventQueue();
+ gQueue.push(new insertListitem("listbox"));
+ gQueue.push(new removeListitem("listbox"));
+ gQueue.invoke(); // Will call SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=656225"
+ title="XUL listbox accessible tree doesn't get updated">
+ Mozilla Bug 656225
+ </a>
+ <br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <listbox id="listbox" rows="2">
+ <listitem label="item2"/>
+ <listitem label="item3"/>
+ <listitem label="item4"/>
+ </listbox>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/treeupdate/test_menu.xul b/accessible/tests/mochitest/treeupdate/test_menu.xul
new file mode 100644
index 000000000..abdea217e
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_menu.xul
@@ -0,0 +1,128 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL menu hierarchy tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function openMenu(aID)
+ {
+ this.menuNode = getNode(aID),
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_FOCUS, this.menuNode)
+ ];
+
+ this.invoke = function openMenu_invoke()
+ {
+ var tree;
+ if (LINUX || SOLARIS) {
+ tree =
+ { PARENT_MENUITEM: [ ] };
+
+ } else {
+ tree =
+ { PARENT_MENUITEM: [
+ { MENUPOPUP: [ ] }
+ ] };
+ }
+ testAccessibleTree(aID, tree);
+
+ // Show menu.
+ this.menuNode.open = true;
+ }
+
+ this.finalCheck = function openMenu_finalCheck()
+ {
+ var tree;
+ if (LINUX || SOLARIS) {
+ tree =
+ { PARENT_MENUITEM: [
+ { MENUITEM: [ ] },
+ { MENUITEM: [ ] }
+ ] };
+
+ } else {
+ tree =
+ { PARENT_MENUITEM: [
+ { MENUPOPUP: [
+ { MENUITEM: [ ] },
+ { MENUITEM: [ ] }
+ ] }
+ ] };
+ }
+ testAccessibleTree(aID, tree);
+ }
+
+ this.getID = function openMenu_getID()
+ {
+ return "open menu " + prettyName(aID);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ var gQueue = null;
+ function doTest()
+ {
+ gQueue = new eventQueue();
+ gQueue.push(new openMenu("menu"));
+ gQueue.invoke(); // Will call SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=249292"
+ title="Ensure accessible children for toolbarbutton types 'menu' and 'menu-button'">
+ Mozilla Bug 249292
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=630486"
+ title="Don't force accessible creation for popup children.">
+ Mozilla Bug 630486
+ </a>
+ <br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <menubar>
+ <menu id="menu" label="menu">
+ <menupopup>
+ <menuitem label="menuitem"/>
+ <menuitem label="menuitem"/>
+ </menupopup>
+ </menu>
+ </menubar>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/treeupdate/test_menubutton.xul b/accessible/tests/mochitest/treeupdate/test_menubutton.xul
new file mode 100644
index 000000000..4821a265b
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_menubutton.xul
@@ -0,0 +1,198 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL button hierarchy tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function openMenu(aButtonID, aMenuItemRole)
+ {
+ var menuItemRole = aMenuItemRole || ROLE_MENUITEM;
+ this.button = getAccessible(aButtonID);
+ this.menupopup = this.button.firstChild;
+
+ var checker = new invokerChecker(EVENT_REORDER, this.menupopup);
+ this.__proto__ = new synthClick(aButtonID, checker);
+
+ this.invoke = function openMenu_invoke()
+ {
+ var tree =
+ { PUSHBUTTON: [
+ { MENUPOPUP: [ ] }
+ ] };
+ testAccessibleTree(this.button, tree);
+
+ this.__proto__.invoke();
+ }
+
+ this.finalCheck = function openMenu_finalCheck()
+ {
+ var tree =
+ { PUSHBUTTON: [
+ { MENUPOPUP: [
+ { role: menuItemRole, children: [ ] },
+ { role: menuItemRole, children: [ ] }
+ ] }
+ ] };
+ testAccessibleTree(this.button, tree);
+
+ synthesizeKey("VK_ESCAPE", { });
+ }
+
+ this.getID = function openMenu_getID()
+ {
+ return "open menu of the button " + prettyName(aButtonID);
+ }
+ }
+
+ function openMenuButton(aButtonID)
+ {
+ this.buttonNode = getNode(aButtonID);
+ this.menupoupNode = this.buttonNode.firstChild;
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.menupoupNode)
+ ];
+
+ this.invoke = function openMenu_invoke()
+ {
+ var tree =
+ { PUSHBUTTON: [
+ { MENUPOPUP: [ ] },
+ { PUSHBUTTON: [ ] }
+ ] };
+ testAccessibleTree(this.buttonNode, tree);
+
+ this.buttonNode.open = true;
+ }
+
+ this.finalCheck = function openMenu_finalCheck()
+ {
+ var tree =
+ { PUSHBUTTON: [
+ { MENUPOPUP: [
+ { MENUITEM: [ ] },
+ { MENUITEM: [ ] }
+ ] },
+ { PUSHBUTTON: [ ] }
+ ] };
+ testAccessibleTree(this.buttonNode, tree);
+
+ this.buttonNode.open = false;
+ }
+
+ this.getID = function openMenu_getID()
+ {
+ return "open menu for menu button " + prettyName(aButtonID);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Do test
+
+ gA11yEventDumpToConsole = true; // debug stuff
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new openMenu("button1"));
+ gQueue.push(new openMenuButton("button2"));
+ gQueue.push(new openMenu("button3"));
+ gQueue.push(new openMenuButton("button4"));
+
+ var columnPickerBtn = getAccessible("tree").firstChild.lastChild;
+ gQueue.push(new openMenu(columnPickerBtn, ROLE_CHECK_MENU_ITEM));
+ gQueue.invoke(); // SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=249292"
+ title="Ensure accessible children for toolbarbutton types 'menu' and 'menu-button'">
+ Bug 249292
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=630486"
+ title="Don't force accessible creation for popup children">
+ Bug 630486
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=722265"
+ title="Column header selection popup no longer exposed to accessibility APIs">
+ Bug 722265
+ </a>
+ <br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <button id="button1" type="menu" label="button">
+ <menupopup>
+ <menuitem label="menuitem"/>
+ <menuitem label="menuitem"/>
+ </menupopup>
+ </button>
+ <button id="button2" type="menu-button" label="menu button">
+ <menupopup>
+ <menuitem label="menuitem"/>
+ <menuitem label="menuitem"/>
+ </menupopup>
+ </button>
+
+ <toolbarbutton id="button3" type="menu" label="toolbarbutton">
+ <menupopup>
+ <menuitem label="menuitem"/>
+ <menuitem label="menuitem"/>
+ </menupopup>
+ </toolbarbutton>
+ <toolbarbutton id="button4" type="menu-button" label="menu toolbarbutton">
+ <menupopup>
+ <menuitem label="menuitem"/>
+ <menuitem label="menuitem"/>
+ </menupopup>
+ </toolbarbutton>
+
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ <treecol id="col2" flex="1" label="another column"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/treeupdate/test_optgroup.html b/accessible/tests/mochitest/treeupdate/test_optgroup.html
new file mode 100644
index 000000000..27323bbc3
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_optgroup.html
@@ -0,0 +1,137 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Add and remove optgroup test</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ function addOptGroup(aID)
+ {
+ this.selectNode = getNode(aID);
+ this.select = getAccessible(this.selectNode);
+ this.selectList = this.select.firstChild;
+
+ this.invoke = function addOptGroup_invoke()
+ {
+ var optGroup = document.createElement("optgroup");
+ for (i = 0; i < 2; i++) {
+ var opt = document.createElement("option");
+ opt.value = i;
+ opt.text = "Option: Value " + i;
+
+ optGroup.appendChild(opt);
+ }
+
+ this.selectNode.add(optGroup, null);
+ var option = document.createElement("option");
+ this.selectNode.add(option, null);
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.selectList)
+ ];
+
+ this.finalCheck = function addOptGroup_finalCheck()
+ {
+ var tree =
+ { COMBOBOX: [
+ { COMBOBOX_LIST: [
+ { GROUPING: [
+ { COMBOBOX_OPTION: [
+ { TEXT_LEAF: [] }
+ ] },
+ { COMBOBOX_OPTION: [
+ { TEXT_LEAF: [] }
+ ] },
+ ]},
+ { COMBOBOX_OPTION: [] }
+ ] }
+ ] };
+ testAccessibleTree(this.select, tree);
+ }
+
+ this.getID = function addOptGroup_getID()
+ {
+ return "test optgroup's insertion into a select";
+ }
+ }
+
+ function removeOptGroup(aID)
+ {
+ this.selectNode = getNode(aID);
+ this.select = getAccessible(this.selectNode);
+ this.selectList = this.select.firstChild;
+
+ this.invoke = function removeOptGroup_invoke()
+ {
+ this.option1Node = this.selectNode.firstChild.firstChild;
+ this.selectNode.removeChild(this.selectNode.firstChild);
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.selectList)
+ ];
+
+ this.finalCheck = function removeOptGroup_finalCheck()
+ {
+ var tree =
+ { COMBOBOX: [
+ { COMBOBOX_LIST: [
+ { COMBOBOX_OPTION: [] }
+ ] }
+ ] };
+ testAccessibleTree(this.select, tree);
+ is(isAccessible(this.option1Node), false, "removed option shouldn't be accessible anymore!");
+ }
+
+ this.getID = function removeOptGroup_getID()
+ {
+ return "test optgroup's removal from a select";
+ }
+ }
+
+ //gA11yEventDumpToConsole = true;
+
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new addOptGroup("select"));
+ gQueue.push(new removeOptGroup("select"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=616452"
+ title="Bug 616452 - Dynamically inserted select options aren't reflected in accessible tree">
+ Bug 616452</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <select id="select"></select>
+
+ <div id="debug"/>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_recreation.html b/accessible/tests/mochitest/treeupdate/test_recreation.html
new file mode 100644
index 000000000..7754eb703
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_recreation.html
@@ -0,0 +1,155 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Test accessible recreation</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function recreateAccessible(aID, aWontBeAccessible)
+ {
+ this.node = getNode(aID);
+ this.accessible =
+ isAccessible(this.node) ? getAccessible(this.node) : null;
+
+ this.eventSeq = [ ];
+
+ if (this.accessible)
+ this.eventSeq.push(new invokerChecker(EVENT_HIDE,
+ this.accessible));
+
+ if (!aWontBeAccessible)
+ this.eventSeq.push(new invokerChecker(EVENT_SHOW, getAccessible,
+ this.node));
+
+ this.eventSeq.push(new invokerChecker(EVENT_REORDER,
+ getContainerAccessible(this.node)));
+
+ if (this.accessible) {
+ this.unexpectedEventSeq = [
+ new invokerChecker(EVENT_SHOW, this.accessible)
+ ];
+ }
+ }
+
+ function changeAttr(aID, aAttr, aValue)
+ {
+ this.__proto__ = new recreateAccessible(aID);
+
+ this.invoke = function changeAttr_invoke()
+ {
+ this.node.setAttribute(aAttr, aValue);
+ }
+
+ this.getID = function changeAttr_getID()
+ {
+ return "change " + aAttr + "attribute for " + aID;
+ }
+ }
+
+ function removeAttr(aID, aAttr)
+ {
+ this.__proto__ = new recreateAccessible(aID, true);
+
+ this.invoke = function remvoeAttr_invoke()
+ {
+ this.node.removeAttribute(aAttr);
+ }
+
+ this.getID = function remvoeAttr_getID()
+ {
+ return "remove " + aAttr + "attribute for " + aID;
+ }
+ }
+
+ function changeRole(aID, aHasAccessible)
+ {
+ this.__proto__ = new changeAttr(aID, "role", "button");
+ }
+
+ function removeRole(aID)
+ {
+ this.__proto__ = new removeAttr(aID, "role");
+ }
+
+ function changeHref(aID)
+ {
+ this.__proto__ = new changeAttr(aID, "href", "www");
+ }
+
+ function changeMultiselectable(aID)
+ {
+ this.__proto__ = new changeAttr(aID, "aria-multiselectable", "true");
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ // make the accessible an inaccessible
+ gQueue.push(new changeRole("span"));
+
+ // make the inaccessible an accessible
+ gQueue.push(new removeRole("span"));
+
+ // recreate an accessible by role change
+ gQueue.push(new changeRole("div1"));
+
+ // recreate an accessible by href change
+ gQueue.push(new changeHref("anchor"));
+
+ // recreate an accessible by aria-multiselectable change
+ gQueue.push(new changeMultiselectable("div3"));
+
+ gQueue.invoke(); // SimpleTest.finish() will be called in the end
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Rework accessible tree update code"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275">
+ Mozilla Bug 570275
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <span id="span">span</span>
+ <div id="div1">div</div>
+ <a id="anchor">anchor</a>
+ <div id="div3" role="listbox">list</div>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_select.html b/accessible/tests/mochitest/treeupdate/test_select.html
new file mode 100644
index 000000000..006618b80
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_select.html
@@ -0,0 +1,130 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Add select options test</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ function addOptions(aID)
+ {
+ this.selectNode = getNode(aID);
+ this.select = getAccessible(this.selectNode);
+ this.selectList = this.select.firstChild;
+
+ this.invoke = function addOptions_invoke()
+ {
+ for (i = 0; i < 2; i++) {
+ var opt = document.createElement("option");
+ opt.value = i;
+ opt.text = "Option: Value " + i;
+
+ this.selectNode.add(opt, null);
+ }
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.selectList)
+ ];
+
+ this.finalCheck = function addOptions_finalCheck()
+ {
+ var tree =
+ { COMBOBOX: [
+ { COMBOBOX_LIST: [
+ { COMBOBOX_OPTION: [
+ { TEXT_LEAF: [] }
+ ] },
+ { COMBOBOX_OPTION: [
+ { TEXT_LEAF: [] }
+ ] }
+ ] }
+ ] };
+ testAccessibleTree(this.select, tree);
+ }
+
+ this.getID = function addOptions_getID()
+ {
+ return "test elements insertion into a select";
+ }
+ }
+
+ function removeOptions(aID)
+ {
+ this.selectNode = getNode(aID);
+ this.select = getAccessible(this.selectNode);
+ this.selectList = this.select.firstChild;
+
+ this.invoke = function removeOptions_invoke()
+ {
+ while (this.selectNode.length)
+ this.selectNode.remove(0);
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.selectList)
+ ];
+
+ this.finalCheck = function removeOptions_finalCheck()
+ {
+ var tree =
+ { COMBOBOX: [
+ { COMBOBOX_LIST: [] }
+ ] };
+ testAccessibleTree(this.select, tree);
+ }
+
+ this.getID = function removeptions_getID()
+ {
+ return "test elements removal from a select";
+ }
+ }
+
+ //gA11yEventDumpID = "debug";
+
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new addOptions("select"));
+ gQueue.push(new removeOptions("select"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish();
+
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=616452"
+ title="Bug 616452 - Dynamically inserted select options aren't reflected in accessible tree">
+ Mozilla Bug 616452</a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=616940"
+ title="Removed select option accessibles aren't removed until hide event is fired">
+ Mozilla Bug 616940</a>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <select id="select"></select>
+
+ <div id="debug"/>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_shutdown.xul b/accessible/tests/mochitest/treeupdate/test_shutdown.xul
new file mode 100644
index 000000000..2e6f7a7b3
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_shutdown.xul
@@ -0,0 +1,132 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL tree hierarchy tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../treeview.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+ <script type="application/javascript"
+ src="../events.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function setXULTreeView(aTreeID, aTreeView)
+ {
+ this.treeNode = getNode(aTreeID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.treeNode)
+ ];
+
+ this.invoke = function loadXULTree_invoke()
+ {
+ this.treeNode.view = aTreeView;
+ };
+
+ this.getID = function loadXULTree_getID()
+ {
+ return "Load XUL tree " + prettyName(aTreeID);
+ };
+ }
+
+ function removeTree(aID)
+ {
+ this.tree = getAccessible(aID);
+ this.lastItem = null;
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, document)
+ ];
+
+ this.invoke = function invoke()
+ {
+ this.lastItem = getAccessible(aID).lastChild;
+ this.lastCell = this.lastItem.lastChild;
+ getNode(aID).parentNode.removeChild(getNode(aID));
+ };
+
+ this.check = function check(aEvent)
+ {
+ testIsDefunct(this.tree, aID);
+ testIsDefunct(this.lastItem, "last item of " + aID);
+ if (this.lastCell) {
+ testIsDefunct(this.lastCell, "last item cell of " + aID);
+ }
+ };
+
+ this.getID = function getID()
+ {
+ return "Remove tree from DOM";
+ };
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ // gA11yEventDumpID = "debug";
+ var gQueue = null;
+
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new setXULTreeView("tree", new nsTreeTreeView()));
+ gQueue.push(new removeTree("tree"));
+
+ gQueue.push(new setXULTreeView("treetable", new nsTreeTreeView()));
+ gQueue.push(new removeTree("treetable"));
+
+ gQueue.invoke(); // Will call SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727"
+ title="Reorganize implementation of XUL tree accessibility">
+ Bug 503727
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <tree id="tree" flex="1">
+ <treecols>
+ <treecol id="col" flex="1" primary="true" label="column"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+
+ <tree id="treetable" flex="1">
+ <treecols>
+ <treecol id="col1" flex="1" primary="true" label="column"/>
+ <treecol id="col2" flex="1" label="column 2"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+ </vbox>
+ </hbox>
+
+</window>
diff --git a/accessible/tests/mochitest/treeupdate/test_table.html b/accessible/tests/mochitest/treeupdate/test_table.html
new file mode 100644
index 000000000..abadefdb0
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_table.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Table update tests</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ function appendCaption(aTableID)
+ {
+ this.invoke = function appendCaption_invoke()
+ {
+ // append a caption, it should appear as a first element in the
+ // accessible tree.
+ var caption = document.createElement("caption");
+ caption.textContent = "table caption";
+ getNode(aTableID).appendChild(caption);
+ }
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, aTableID)
+ ];
+
+ this.finalCheck = function appendCaption_finalCheck()
+ {
+ var tree =
+ { TABLE: [
+ { CAPTION: [
+ { TEXT_LEAF: [] }
+ ] },
+ { ROW: [
+ { CELL: [ {TEXT_LEAF: [] }]},
+ { CELL: [ {TEXT_LEAF: [] }]}
+ ] }
+ ] };
+ testAccessibleTree(aTableID, tree);
+ }
+
+ this.getID = function appendCaption_getID()
+ {
+ return "append caption";
+ }
+ }
+
+ function doTest()
+ {
+ gQueue = new eventQueue();
+ gQueue.push(new appendCaption("table"));
+ gQueue.invoke(); // Will call SimpleTest.finish();
+
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <table id="table">
+ <tr>
+ <td>cell1</td>
+ <td>cell2</td>
+ </tr>
+ </table>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_textleaf.html b/accessible/tests/mochitest/treeupdate/test_textleaf.html
new file mode 100644
index 000000000..16d3a1a2b
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_textleaf.html
@@ -0,0 +1,180 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Test accessible recreation</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ function textLeafUpdate(aID, aIsTextLeafLinkable)
+ {
+ this.node = getNode(aID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.node.parentNode)
+ ];
+
+ this.finalCheck = function textLeafUpdate_finalCheck()
+ {
+ var textLeaf = getAccessible(this.node).firstChild;
+ is(textLeaf.actionCount, (aIsTextLeafLinkable ? 1 : 0),
+ "Wrong action numbers!");
+ }
+ }
+
+ function setOnClickAttr(aID)
+ {
+ var node = getNode(aID);
+ node.setAttribute("onclick", "alert(3);");
+ var textLeaf = getAccessible(node).firstChild;
+ is(textLeaf.actionCount, 1, "setOnClickAttr: wrong action numbers!");
+ }
+
+ function removeOnClickAttr(aID)
+ {
+ var node = getNode(aID);
+ node.removeAttribute("onclick");
+ var textLeaf = getAccessible(node).firstChild;
+ is(textLeaf.actionCount, 0,
+ "removeOnClickAttr: wrong action numbers!");
+ }
+
+ function setOnClickNRoleAttrs(aID)
+ {
+ this.__proto__ = new textLeafUpdate(aID, true);
+
+ this.invoke = function setOnClickAttr_invoke()
+ {
+ this.node.setAttribute("role", "link");
+ this.node.setAttribute("onclick", "alert(3);");
+ }
+
+ this.getID = function setOnClickAttr_getID()
+ {
+ return "make " + prettyName(aID) + " linkable";
+ }
+ }
+
+ function removeTextData(aID, aRole)
+ {
+ this.containerNode = getNode(aID);
+ this.textNode = this.containerNode.firstChild;
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.containerNode)
+ ];
+
+ this.invoke = function removeTextData_invoke()
+ {
+ var tree = {
+ role: aRole,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "text"
+ }
+ ]
+ };
+ testAccessibleTree(this.containerNode, tree);
+
+ this.textNode.data = "";
+ }
+
+ this.finalCheck = function removeTextData_finalCheck()
+ {
+ var tree = {
+ role: aRole,
+ children: []
+ };
+ testAccessibleTree(this.containerNode, tree);
+ }
+
+ this.getID = function removeTextData_finalCheck()
+ {
+ return "remove text data of text node inside '" + aID + "'.";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ // adds onclick on element, text leaf should inherit its action
+ setOnClickAttr("div");
+ // remove onclick attribute, text leaf shouldn't have any action
+ removeOnClickAttr("div");
+
+ // Call rest of event tests.
+ gQueue = new eventQueue();
+
+ // set onclick attribute making span accessible, it's inserted into tree
+ // and adopts text leaf accessible, text leaf should have an action
+ gQueue.push(new setOnClickNRoleAttrs("span"));
+
+ // text data removal of text node should remove its text accessible
+ gQueue.push(new removeTextData("p", ROLE_PARAGRAPH));
+ gQueue.push(new removeTextData("pre", ROLE_TEXT_CONTAINER));
+
+ gQueue.invoke(); // SimpleTest.finish() will be called in the end
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Clean up the code of accessible initialization and binding to the tree"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=545465">
+ Mozilla Bug 545465
+ </a>
+ <a target="_blank"
+ title="Make sure accessible tree is correct when rendered text is changed"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=625652">
+ Mozilla Bug 625652
+ </a>
+ <a target="_blank"
+ title="Remove text accesible getting no text inside a preformatted area"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=706335">
+ Mozilla Bug 706335
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="container">
+ <div id="div">div</div>
+ <span id="span">span</span>
+ </div>
+
+ <p id="p">text</p>
+ <pre id="pre">text</pre>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_visibility.html b/accessible/tests/mochitest/treeupdate/test_visibility.html
new file mode 100644
index 000000000..a1c130fb6
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_visibility.html
@@ -0,0 +1,437 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Style visibility tree update test</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ /**
+ * Hide parent while child stays visible.
+ */
+ function test1(aContainerID, aParentID, aChildID)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode(aParentID)),
+ new invokerChecker(EVENT_SHOW, getNode(aChildID)),
+ new invokerChecker(EVENT_REORDER, getNode(aContainerID))
+ ];
+
+ this.invoke = function invoke()
+ {
+ var tree =
+ { SECTION: [
+ { SECTION: [
+ { SECTION: [
+ { TEXT_LEAF: [] }
+ ] }
+ ] }
+ ] };
+ testAccessibleTree(aContainerID, tree);
+
+ getNode(aParentID).style.visibility = "hidden";
+ }
+
+ this.finalCheck = function finalCheck()
+ {
+ var tree =
+ { SECTION: [
+ { SECTION: [
+ { TEXT_LEAF: [] }
+ ] }
+ ] };
+ testAccessibleTree(aContainerID, tree);
+ }
+
+ this.getID = function getID()
+ {
+ return "hide parent while child stays visible";
+ }
+ }
+
+ /**
+ * Hide grand parent while its children stay visible.
+ */
+ function test2(aContainerID, aGrandParentID, aChildID, aChild2ID)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode(aGrandParentID)),
+ new invokerChecker(EVENT_SHOW, getNode(aChildID)),
+ new invokerChecker(EVENT_SHOW, getNode(aChild2ID)),
+ new invokerChecker(EVENT_REORDER, getNode(aContainerID))
+ ];
+
+ this.invoke = function invoke()
+ {
+ var tree =
+ { SECTION: [ // container
+ { SECTION: [ // grand parent
+ { SECTION: [
+ { SECTION: [ // child
+ { TEXT_LEAF: [] }
+ ] },
+ { SECTION: [ // child2
+ { TEXT_LEAF: [] }
+ ] }
+ ] }
+ ] }
+ ] };
+ testAccessibleTree(aContainerID, tree);
+
+ getNode(aGrandParentID).style.visibility = "hidden";
+ }
+
+ this.finalCheck = function finalCheck()
+ {
+ var tree =
+ { SECTION: [ // container
+ { SECTION: [ // child
+ { TEXT_LEAF: [] }
+ ] },
+ { SECTION: [ // child2
+ { TEXT_LEAF: [] }
+ ] }
+ ] };
+ testAccessibleTree(aContainerID, tree);
+ }
+
+ this.getID = function getID()
+ {
+ return "hide grand parent while its children stay visible";
+ }
+ }
+
+ /**
+ * Change container style, hide parents while their children stay visible.
+ */
+ function test3(aContainerID, aParentID, aParent2ID, aChildID, aChild2ID)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode(aParentID)),
+ new invokerChecker(EVENT_HIDE, getNode(aParent2ID)),
+ new invokerChecker(EVENT_SHOW, getNode(aChildID)),
+ new invokerChecker(EVENT_SHOW, getNode(aChild2ID)),
+ new invokerChecker(EVENT_REORDER, getNode(aContainerID))
+ ];
+
+ this.invoke = function invoke()
+ {
+ var tree =
+ { SECTION: [ // container
+ { SECTION: [ // parent
+ { SECTION: [ // child
+ { TEXT_LEAF: [] }
+ ] }
+ ] },
+ { SECTION: [ // parent2
+ { SECTION: [ // child2
+ { TEXT_LEAF: [] }
+ ] },
+ ] }
+ ] };
+ testAccessibleTree(aContainerID, tree);
+
+ getNode(aContainerID).style.color = "red";
+ getNode(aParentID).style.visibility = "hidden";
+ getNode(aParent2ID).style.visibility = "hidden";
+ }
+
+ this.finalCheck = function finalCheck()
+ {
+ var tree =
+ { SECTION: [ // container
+ { SECTION: [ // child
+ { TEXT_LEAF: [] }
+ ] },
+ { SECTION: [ // child2
+ { TEXT_LEAF: [] }
+ ] }
+ ] };
+ testAccessibleTree(aContainerID, tree);
+ }
+
+ this.getID = function getID()
+ {
+ return "change container style, hide parents while their children stay visible";
+ }
+ }
+
+ /**
+ * Change container style and make visible child inside the table.
+ */
+ function test4(aContainerID, aChildID)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_SHOW, getNode(aChildID)),
+ new invokerChecker(EVENT_REORDER, getNode(aChildID).parentNode)
+ ];
+
+ this.invoke = function invoke()
+ {
+ var tree =
+ { SECTION: [
+ { TABLE: [
+ { ROW: [
+ { CELL: [ ] }
+ ] }
+ ] }
+ ] };
+ testAccessibleTree(aContainerID, tree);
+
+ getNode(aContainerID).style.color = "red";
+ getNode(aChildID).style.visibility = "visible";
+ }
+
+ this.finalCheck = function finalCheck()
+ {
+ var tree =
+ { SECTION: [
+ { TABLE: [
+ { ROW: [
+ { CELL: [
+ { SECTION: [
+ { TEXT_LEAF: [] }
+ ] }
+ ] }
+ ] }
+ ] }
+ ] };
+ testAccessibleTree(aContainerID, tree);
+ }
+
+ this.getID = function getID()
+ {
+ return "change container style, make visible child insdie the table";
+ }
+ }
+
+ /**
+ * Hide subcontainer while child inside the table stays visible.
+ */
+ function test5(aContainerID, aSubContainerID, aChildID)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode(aSubContainerID)),
+ new invokerChecker(EVENT_SHOW, getNode(aChildID)),
+ new invokerChecker(EVENT_REORDER, getNode(aContainerID))
+ ];
+
+ this.invoke = function invoke()
+ {
+ var tree =
+ { SECTION: [ // container
+ { SECTION: [ // subcontainer
+ { TABLE: [
+ { ROW: [
+ { CELL: [
+ { SECTION: [ // child
+ { TEXT_LEAF: [] }
+ ] }
+ ] }
+ ] }
+ ] }
+ ] }
+ ] };
+ testAccessibleTree(aContainerID, tree);
+
+ getNode(aSubContainerID).style.visibility = "hidden";
+ }
+
+ this.finalCheck = function finalCheck()
+ {
+ var tree =
+ { SECTION: [ // container
+ { SECTION: [ // child
+ { TEXT_LEAF: [] }
+ ] }
+ ] };
+ testAccessibleTree(aContainerID, tree);
+ }
+
+ this.getID = function getID()
+ {
+ return "hide subcontainer while child inside the table stays visible";
+ }
+ }
+
+ /**
+ * Hide subcontainer while its child and child inside the nested table stays visible.
+ */
+ function test6(aContainerID, aSubContainerID, aChildID, aChild2ID)
+ {
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, getNode(aSubContainerID)),
+ new invokerChecker(EVENT_SHOW, getNode(aChildID)),
+ new invokerChecker(EVENT_SHOW, getNode(aChild2ID)),
+ new invokerChecker(EVENT_REORDER, getNode(aContainerID))
+ ];
+
+ this.invoke = function invoke()
+ {
+ var tree =
+ { SECTION: [ // container
+ { SECTION: [ // subcontainer
+ { TABLE: [
+ { ROW: [
+ { CELL: [
+ { TABLE: [ // nested table
+ { ROW: [
+ { CELL: [
+ { SECTION: [ // child
+ { TEXT_LEAF: [] } ]} ]} ]} ]} ]} ]} ]},
+ { SECTION: [ // child2
+ { TEXT_LEAF: [] } ]} ]} ]};
+
+ testAccessibleTree(aContainerID, tree);
+
+ // invoke
+ getNode(aSubContainerID).style.visibility = "hidden";
+ }
+
+ this.finalCheck = function finalCheck()
+ {
+ var tree =
+ { SECTION: [ // container
+ { SECTION: [ // child
+ { TEXT_LEAF: [] } ]},
+ { SECTION: [ // child2
+ { TEXT_LEAF: [] } ]} ]};
+
+ testAccessibleTree(aContainerID, tree);
+ }
+
+ this.getID = function getID()
+ {
+ return "hide subcontainer while its child and child inside the nested table stays visible";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new test1("t1_container", "t1_parent", "t1_child"));
+ gQueue.push(new test2("t2_container", "t2_grandparent", "t2_child", "t2_child2"));
+ gQueue.push(new test3("t3_container", "t3_parent", "t3_parent2", "t3_child", "t3_child2"));
+ gQueue.push(new test4("t4_container", "t4_child"));
+ gQueue.push(new test5("t5_container", "t5_subcontainer", "t5_child"));
+ gQueue.push(new test6("t6_container", "t6_subcontainer", "t6_child", "t6_child2"));
+
+ gQueue.invoke(); // SimpleTest.finish() will be called in the end
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Develop a way to handle visibility style"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=606125">
+ Mozilla Bug 606125
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <!-- hide parent while child stays visible -->
+ <div id="t1_container">
+ <div id="t1_parent">
+ <div id="t1_child" style="visibility: visible">text</div>
+ </div>
+ </div>
+
+ <!-- hide grandparent while its children stay visible -->
+ <div id="t2_container">
+ <div id="t2_grandparent">
+ <div>
+ <div id="t2_child" style="visibility: visible">text</div>
+ <div id="t2_child2" style="visibility: visible">text</div>
+ </div>
+ </div>
+ </div>
+
+ <!-- change container style, hide parents while their children stay visible -->
+ <div id="t3_container">
+ <div id="t3_parent">
+ <div id="t3_child" style="visibility: visible">text</div>
+ </div>
+ <div id="t3_parent2">
+ <div id="t3_child2" style="visibility: visible">text</div>
+ </div>
+ </div>
+
+ <!-- change container style, show child inside the table -->
+ <div id="t4_container">
+ <table>
+ <tr>
+ <td>
+ <div id="t4_child" style="visibility: hidden;">text</div>
+ </td>
+ </tr>
+ </table>
+ </div>
+
+ <!-- hide subcontainer while child inside the table stays visible -->
+ <div id="t5_container">
+ <div id="t5_subcontainer">
+ <table>
+ <tr>
+ <td>
+ <div id="t5_child" style="visibility: visible;">text</div>
+ </td>
+ </tr>
+ </table>
+ </div>
+ </div>
+
+ <!-- hide subcontainer while its child and child inside the nested table stays visible -->
+ <div id="t6_container">
+ <div id="t6_subcontainer">
+ <table>
+ <tr>
+ <td>
+ <table>
+ <tr>
+ <td>
+ <div id="t6_child" style="visibility: visible;">text</div>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ <div id="t6_child2" style="visibility: visible">text</div>
+ </div>
+ </div>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeupdate/test_whitespace.html b/accessible/tests/mochitest/treeupdate/test_whitespace.html
new file mode 100644
index 000000000..e7ba9b059
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_whitespace.html
@@ -0,0 +1,187 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Whitespace text accessible creation/desctruction</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../role.js"></script>
+ <script type="application/javascript"
+ src="../events.js"></script>
+
+ <script type="application/javascript">
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Invokers
+
+ /**
+ * Middle image accessible removal results in text accessible removal.
+ *
+ * Before:
+ * DOM: whitespace img1 whitespace img2 whitespace img3 whitespace,
+ * a11y: img1 whitespace img2 whitespace img3
+ * After:
+ * DOM: whitespace img1 whitespace whitespace img3 whitespace,
+ * a11y: img1 whitespace img3
+ */
+ function removeImg()
+ {
+ this.containerNode = getNode("container1");
+ this.imgNode = getNode("img1");
+ this.img = getAccessible(this.imgNode);
+ this.text = this.img.nextSibling;
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_HIDE, this.img),
+ new invokerChecker(EVENT_HIDE, this.text),
+ new invokerChecker(EVENT_REORDER, this.containerNode)
+ ];
+
+ this.finalCheck = function textLeafUpdate_finalCheck()
+ {
+ var tree =
+ { SECTION: [
+ { GRAPHIC: [] },
+ { TEXT_LEAF: [] },
+ { GRAPHIC: [] }
+ ] };
+
+ testAccessibleTree(this.containerNode, tree);
+ }
+
+ this.invoke = function setOnClickAttr_invoke()
+ {
+ var tree =
+ { SECTION: [
+ { GRAPHIC: [] },
+ { TEXT_LEAF: [] },
+ { GRAPHIC: [] },
+ { TEXT_LEAF: [] },
+ { GRAPHIC: [] }
+ ] };
+
+ testAccessibleTree(this.containerNode, tree);
+
+ this.containerNode.removeChild(this.imgNode);
+ }
+
+ this.getID = function setOnClickAttr_getID()
+ {
+ return "remove middle img";
+ }
+ }
+
+ /**
+ * Append image making the whitespace visible and thus accessible.
+ * Note: images and whitespaces are on different leves of accessible trees,
+ * so that image container accessible update doesn't update the tree
+ * of whitespace container.
+ *
+ * Before:
+ * DOM: whitespace emptylink whitespace linkwithimg whitespace
+ * a11y: emptylink linkwithimg
+ * After:
+ * DOM: whitespace linkwithimg whitespace linkwithimg whitespace
+ * a11y: linkwithimg whitespace linkwithimg
+ */
+ function insertImg()
+ {
+ this.containerNode = getNode("container2");
+ this.topNode = this.containerNode.parentNode;
+ this.textNode = this.containerNode.nextSibling;
+ this.imgNode = document.createElement("img");
+ this.imgNode.setAttribute("src", "../moz.png");
+
+ this.eventSeq = [
+ new asyncInvokerChecker(EVENT_SHOW, getAccessible, this.textNode),
+ new asyncInvokerChecker(EVENT_SHOW, getAccessible, this.imgNode),
+ new orderChecker(),
+ new invokerChecker(EVENT_REORDER, this.topNode)
+ ];
+
+ this.invoke = function insertImg_invoke()
+ {
+ var tree =
+ { SECTION: [
+ { LINK: [] },
+ { LINK: [
+ { GRAPHIC: [] }
+ ] }
+ ] };
+
+ testAccessibleTree(this.topNode, tree);
+
+ this.containerNode.appendChild(this.imgNode);
+ }
+
+ this.finalCheck = function insertImg_finalCheck()
+ {
+ var tree =
+ { SECTION: [
+ { LINK: [
+ { GRAPHIC: [ ] }
+ ] },
+ { TEXT_LEAF: [ ] },
+ { LINK: [
+ { GRAPHIC: [ ] }
+ ] }
+ ] };
+
+ testAccessibleTree(this.topNode, tree);
+ }
+
+ this.getID = function appendImg_getID()
+ {
+ return "insert img into internal container";
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Test
+
+ //gA11yEventDumpID = "eventdump"; // debug stuff
+ //gA11yEventDumpToConsole = true;
+
+ var gQueue = null;
+
+ function doTest()
+ {
+ gQueue = new eventQueue();
+
+ gQueue.push(new removeImg());
+ gQueue.push(new insertImg());
+
+ gQueue.invoke(); // SimpleTest.finish() will be called in the end
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+</head>
+<body>
+
+ <a target="_blank"
+ title="Make sure accessible tree is correct when rendered text is changed"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=625652">
+ Mozilla Bug 625652
+ </a>
+
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test">
+ </pre>
+
+ <div id="container1"> <img src="../moz.png"> <img id="img1" src="../moz.png"> <img src="../moz.png"> </div>
+ <div> <a id="container2"></a> <a><img src="../moz.png"></a> </div>
+
+ <div id="eventdump"></div>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/treeview.css b/accessible/tests/mochitest/treeview.css
new file mode 100644
index 000000000..1bffb1798
--- /dev/null
+++ b/accessible/tests/mochitest/treeview.css
@@ -0,0 +1,15 @@
+treechildren::-moz-tree-checkbox(checked) {
+ list-style-image: url("chrome://global/skin/checkbox/cbox-check.gif");
+}
+
+treechildren::-moz-tree-image(cyclerState1) {
+ list-style-image: url("chrome://global/skin/console/bullet-question.png");
+}
+
+treechildren::-moz-tree-image(cyclerState2) {
+ list-style-image: url("chrome://global/skin/console/bullet-warning.png");
+}
+
+treechildren::-moz-tree-image(cyclerState3) {
+ list-style-image: url("chrome://global/skin/console/bullet-error.png");
+}
diff --git a/accessible/tests/mochitest/treeview.js b/accessible/tests/mochitest/treeview.js
new file mode 100644
index 000000000..869471c85
--- /dev/null
+++ b/accessible/tests/mochitest/treeview.js
@@ -0,0 +1,289 @@
+/**
+ * Helper method to start a single XUL tree test.
+ */
+function loadXULTreeAndDoTest(aDoTestFunc, aTreeID, aTreeView)
+{
+ var doTestFunc = aDoTestFunc ? aDoTestFunc : gXULTreeLoadContext.doTestFunc;
+ var treeID = aTreeID ? aTreeID : gXULTreeLoadContext.treeID;
+ var treeView = aTreeView ? aTreeView : gXULTreeLoadContext.treeView;
+
+ function loadXULTree(aTreeID, aTreeView)
+ {
+ this.treeNode = getNode(aTreeID);
+
+ this.eventSeq = [
+ new invokerChecker(EVENT_REORDER, this.treeNode)
+ ];
+
+ this.invoke = function loadXULTree_invoke()
+ {
+ this.treeNode.view = aTreeView;
+ }
+
+ this.getID = function loadXULTree_getID()
+ {
+ return "Load XUL tree " + prettyName(aTreeID);
+ }
+ }
+
+ gXULTreeLoadContext.queue = new eventQueue();
+ gXULTreeLoadContext.queue.push(new loadXULTree(treeID, treeView));
+ gXULTreeLoadContext.queue.onFinish = function()
+ {
+ SimpleTest.executeSoon(doTestFunc);
+ return DO_NOT_FINISH_TEST;
+ }
+ gXULTreeLoadContext.queue.invoke();
+}
+
+/**
+ * Analogy of addA11yLoadEvent, nice helper to load XUL tree and start the test.
+ */
+function addA11yXULTreeLoadEvent(aDoTestFunc, aTreeID, aTreeView)
+{
+ gXULTreeLoadContext.doTestFunc = aDoTestFunc;
+ gXULTreeLoadContext.treeID = aTreeID;
+ gXULTreeLoadContext.treeView = aTreeView;
+
+ addA11yLoadEvent(loadXULTreeAndDoTest);
+}
+
+
+function nsTableTreeView(aRowCount)
+{
+ this.__proto__ = new nsTreeView();
+
+ for (var idx = 0; idx < aRowCount; idx++)
+ this.mData.push(new treeItem("row" + String(idx) + "_"));
+}
+
+function nsTreeTreeView()
+{
+ this.__proto__ = new nsTreeView();
+
+ this.mData = [
+ new treeItem("row1"),
+ new treeItem("row2_", true, [new treeItem("row2.1_"), new treeItem("row2.2_")]),
+ new treeItem("row3_", false, [new treeItem("row3.1_"), new treeItem("row3.2_")]),
+ new treeItem("row4")
+ ];
+}
+
+function nsTreeView()
+{
+ this.mTree = null;
+ this.mData = [];
+}
+
+nsTreeView.prototype =
+{
+ //////////////////////////////////////////////////////////////////////////////
+ // nsITreeView implementation
+
+ get rowCount()
+ {
+ return this.getRowCountIntl(this.mData);
+ },
+ setTree: function setTree(aTree)
+ {
+ this.mTree = aTree;
+ },
+ getCellText: function getCellText(aRow, aCol)
+ {
+ var data = this.getDataForIndex(aRow);
+ if (aCol.id in data.colsText)
+ return data.colsText[aCol.id];
+
+ return data.text + aCol.id;
+ },
+ getCellValue: function getCellValue(aRow, aCol)
+ {
+ var data = this.getDataForIndex(aRow);
+ return data.value;
+ },
+ getRowProperties: function getRowProperties(aIndex) { return ""; },
+ getCellProperties: function getCellProperties(aIndex, aCol)
+ {
+ if (!aCol.cycler)
+ return "";
+
+ var data = this.getDataForIndex(aIndex);
+ return this.mCyclerStates[data.cyclerState];
+ },
+ getColumnProperties: function getColumnProperties(aCol) { return ""; },
+ getParentIndex: function getParentIndex(aRowIndex)
+ {
+ var info = this.getInfoByIndex(aRowIndex);
+ return info.parentIndex;
+ },
+ hasNextSibling: function hasNextSibling(aRowIndex, aAfterIndex) { },
+ getLevel: function getLevel(aIndex)
+ {
+ var info = this.getInfoByIndex(aIndex);
+ return info.level;
+ },
+ getImageSrc: function getImageSrc(aRow, aCol) {},
+ getProgressMode: function getProgressMode(aRow, aCol) {},
+ isContainer: function isContainer(aIndex)
+ {
+ var data = this.getDataForIndex(aIndex);
+ return data.open != undefined;
+ },
+ isContainerOpen: function isContainerOpen(aIndex)
+ {
+ var data = this.getDataForIndex(aIndex);
+ return data.open;
+ },
+ isContainerEmpty: function isContainerEmpty(aIndex)
+ {
+ var data = this.getDataForIndex(aIndex);
+ return data.open == undefined;
+ },
+ isSeparator: function isSeparator(aIndex) {},
+ isSorted: function isSorted() {},
+ toggleOpenState: function toggleOpenState(aIndex)
+ {
+ var data = this.getDataForIndex(aIndex);
+
+ data.open = !data.open;
+ var rowCount = this.getRowCountIntl(data.children);
+
+ if (data.open)
+ this.mTree.rowCountChanged(aIndex + 1, rowCount);
+ else
+ this.mTree.rowCountChanged(aIndex + 1, -rowCount);
+ },
+ selectionChanged: function selectionChanged() {},
+ cycleHeader: function cycleHeader(aCol) {},
+ cycleCell: function cycleCell(aRow, aCol)
+ {
+ var data = this.getDataForIndex(aRow);
+ data.cyclerState = (data.cyclerState + 1) % 3;
+
+ this.mTree.invalidateCell(aRow, aCol);
+ },
+ isEditable: function isEditable(aRow, aCol)
+ {
+ return true;
+ },
+ isSelectable: function isSelectable(aRow, aCol) {},
+ setCellText: function setCellText(aRow, aCol, aValue)
+ {
+ var data = this.getDataForIndex(aRow);
+ data.colsText[aCol.id] = aValue;
+ },
+ setCellValue: function setCellValue(aRow, aCol, aValue)
+ {
+ var data = this.getDataForIndex(aRow);
+ data.value = aValue;
+
+ this.mTree.invalidateCell(aRow, aCol);
+ },
+ performAction: function performAction(aAction) {},
+ performActionOnRow: function performActionOnRow(aAction, aRow) {},
+ performActionOnCell: function performActionOnCell(aAction, aRow, aCol) {},
+
+ //////////////////////////////////////////////////////////////////////////////
+ // public implementation
+
+ appendItem: function appendItem(aText)
+ {
+ this.mData.push(new treeItem(aText));
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ // private implementation
+
+ getDataForIndex: function getDataForIndex(aRowIndex)
+ {
+ return this.getInfoByIndex(aRowIndex).data;
+ },
+
+ getInfoByIndex: function getInfoByIndex(aRowIndex)
+ {
+ var info = {
+ data: null,
+ parentIndex: -1,
+ level: 0,
+ index: -1
+ };
+
+ this.getInfoByIndexIntl(aRowIndex, info, this.mData, 0);
+ return info;
+ },
+
+ getRowCountIntl: function getRowCountIntl(aChildren)
+ {
+ var rowCount = 0;
+ for (var childIdx = 0; childIdx < aChildren.length; childIdx++) {
+ rowCount++;
+
+ var data = aChildren[childIdx];
+ if (data.open)
+ rowCount += this.getRowCountIntl(data.children);
+ }
+
+ return rowCount;
+ },
+
+ getInfoByIndexIntl: function getInfoByIndexIntl(aRowIdx, aInfo,
+ aChildren, aLevel)
+ {
+ var rowIdx = aRowIdx;
+ for (var childIdx = 0; childIdx < aChildren.length; childIdx++) {
+ var data = aChildren[childIdx];
+
+ aInfo.index++;
+
+ if (rowIdx == 0) {
+ aInfo.data = data;
+ aInfo.level = aLevel;
+ return -1;
+ }
+
+ if (data.open) {
+ var parentIdx = aInfo.index;
+ rowIdx = this.getInfoByIndexIntl(rowIdx - 1, aInfo, data.children,
+ aLevel + 1);
+
+ if (rowIdx == -1) {
+ if (aInfo.parentIndex == -1)
+ aInfo.parentIndex = parentIdx;
+ return 0;
+ }
+ } else {
+ rowIdx--;
+ }
+ }
+
+ return rowIdx;
+ },
+
+ mCyclerStates: [
+ "cyclerState1",
+ "cyclerState2",
+ "cyclerState3"
+ ]
+};
+
+function treeItem(aText, aOpen, aChildren)
+{
+ this.text = aText;
+ this.colsText = {};
+ this.open = aOpen;
+ this.value = "true";
+ this.cyclerState = 0;
+ if (aChildren)
+ this.children = aChildren;
+}
+
+/**
+ * Used in conjunction with loadXULTreeAndDoTest and addA11yXULTreeLoadEvent.
+ */
+var gXULTreeLoadContext =
+{
+ doTestFunc: null,
+ treeID: null,
+ treeView: null,
+ queue: null
+};
diff --git a/accessible/tests/mochitest/value.js b/accessible/tests/mochitest/value.js
new file mode 100644
index 000000000..cc9dffe00
--- /dev/null
+++ b/accessible/tests/mochitest/value.js
@@ -0,0 +1,32 @@
+////////////////////////////////////////////////////////////////////////////////
+// Public methods
+
+/**
+ * Tests nsIAccessibleValue interface.
+ *
+ * @param aAccOrElmOrId [in] identifier of accessible
+ * @param aValue [in] accessible value (nsIAccessible::value)
+ * @param aCurrValue [in] current value (nsIAccessibleValue::currentValue)
+ * @param aMinValue [in] minimum value (nsIAccessibleValue::minimumValue)
+ * @param aMaxValue [in] maximumn value (nsIAccessibleValue::maximumValue)
+ * @param aMinIncr [in] minimum increment value
+ * (nsIAccessibleValue::minimumIncrement)
+ */
+function testValue(aAccOrElmOrId, aValue, aCurrValue,
+ aMinValue, aMaxValue, aMinIncr)
+{
+ var acc = getAccessible(aAccOrElmOrId, [nsIAccessibleValue]);
+ if (!acc)
+ return;
+
+ is(acc.value, aValue, "Wrong value of " + prettyName(aAccOrElmOrId));
+
+ is(acc.currentValue, aCurrValue,
+ "Wrong current value of " + prettyName(aAccOrElmOrId));
+ is(acc.minimumValue, aMinValue,
+ "Wrong minimum value of " + prettyName(aAccOrElmOrId));
+ is(acc.maximumValue, aMaxValue,
+ "Wrong maximum value of " + prettyName(aAccOrElmOrId));
+ is(acc.minimumIncrement, aMinIncr,
+ "Wrong minimum increment value of " + prettyName(aAccOrElmOrId));
+}
diff --git a/accessible/tests/mochitest/value/a11y.ini b/accessible/tests/mochitest/value/a11y.ini
new file mode 100644
index 000000000..b93d9b180
--- /dev/null
+++ b/accessible/tests/mochitest/value/a11y.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+support-files =
+ !/accessible/tests/mochitest/*.js
+
+[test_general.html]
+[test_number.html]
+[test_progress.html]
+[test_progress.xul]
+[test_range.html]
diff --git a/accessible/tests/mochitest/value/test_general.html b/accessible/tests/mochitest/value/test_general.html
new file mode 100644
index 000000000..12e718ba1
--- /dev/null
+++ b/accessible/tests/mochitest/value/test_general.html
@@ -0,0 +1,159 @@
+<html>
+
+<head>
+ <title>nsIAccessible value testing</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <style type="text/css">
+ .offscreen {
+ position: absolute;
+ left: -5000px;
+ top: -5000px;
+ height: 100px;
+ width: 100px;
+ }
+ </style>
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/chrome-harness.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ function testValue(aID, aValue)
+ {
+ var acc = getAccessible(aID);
+ if (!acc)
+ return;
+ is(acc.value, aValue, "Wrong value for " + aID + "!");
+ }
+
+ var rootDir = getRootDirectory(window.location.href);
+ var href = getRootDirectory(window.location.href) + "foo";
+
+ // roles that can't live as HTMLLinkAccessibles
+ testValue("aria_menuitem_link", "");
+ testValue("aria_button_link", "");
+ testValue("aria_checkbox_link", "");
+ testValue("aria_application_link", "");
+
+ // roles that can live as HTMLLinkAccessibles
+ testValue("aria_link_link", href);
+ testValue("aria_main_link", href);
+ testValue("aria_navigation_link", href);
+
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA textboxes
+
+ testValue("aria_textbox1", "helo");
+
+ //////////////////////////////////////////////////////////////////////////
+ // ARIA comboboxes
+
+ // aria-activedescendant defines a current item the value is computed from
+ testValue("aria_combobox1", kDiscBulletText + "Zoom");
+
+ // aria-selected defines a selected item the value is computed from,
+ // list control is pointed by aria-owns relation.
+ testValue("aria_combobox2", kDiscBulletText + "Zoom");
+
+ // aria-selected defines a selected item the value is computed from,
+ // list control is a child of combobox.
+ testValue("aria_combobox3", kDiscBulletText + "2");
+
+ //////////////////////////////////////////////////////////////////////////
+ // HTML controls
+ testValue("combobox1", "item1");
+ testValue("combobox2", "item2");
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=494807"
+ title="Do not expose a11y info specific to hyperlinks when role is overridden using ARIA">
+ Bug 494807
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=819273"
+ title="ARIA combobox should have accessible value">
+ Bug 819273
+ </a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=887250"
+ title="ARIA textbox role doesn't expose value">
+ Bug 887250
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+
+ <a id="aria_menuitem_link" role="menuitem" href="foo">menuitem</a>
+ <a id="aria_button_link" role="button" href="foo">button</a>
+ <a id="aria_checkbox_link" role="checkbox" href="foo">checkbox</a>
+
+ <!-- landmark links -->
+ <a id="aria_application_link" role="application" href="foo">app</a>
+ <a id="aria_main_link" role="main" href="foo">main</a>
+ <a id="aria_navigation_link" role="navigation" href="foo">nav</a>
+
+ <!-- strange edge case: please don't do this in the wild -->
+ <a id="aria_link_link" role="link" href="foo">link</a>
+
+ <div id="aria_textbox1" role="textbox">helo</div>
+
+ <div id="aria_combobox1" role="combobox"
+ aria-owns="aria_combobox1_owned_listbox"
+ aria-activedescendant="aria_combobox1_selected_option">
+ </div>
+ <ul role="listbox" id="aria_combobox1_owned_listbox">
+ <li role="option">Zebra</li>
+ <li role="option" id="aria_combobox1_selected_option">Zoom</li>
+ </ul>
+
+ <div id="aria_combobox2" role="combobox"
+ aria-owns="aria_combobox2_owned_listbox">
+ </div>
+ <ul role="listbox" id="aria_combobox2_owned_listbox">
+ <li role="option">Zebra</li>
+ <li role="option" aria-selected="true">Zoom</li>
+ </ul>
+
+ <div id="aria_combobox3" role="combobox">
+ <div role="textbox"></div>
+ <ul role="listbox">
+ <li role="option">1</li>
+ <li role="option" aria-selected="true">2</li>
+ <li role="option">3</li>
+ </ul>
+ </div>
+
+ <select id="combobox1">
+ <option id="cb1_item1">item1</option>
+ <option id="cb1_item2">item2</option>
+ </select>
+ <select id="combobox2">
+ <option id="cb2_item1">item1</option>
+ <option id="cb2_item2" selected="true">item2</option>
+ </select>
+
+</body>
+</html>
diff --git a/accessible/tests/mochitest/value/test_number.html b/accessible/tests/mochitest/value/test_number.html
new file mode 100644
index 000000000..68825b445
--- /dev/null
+++ b/accessible/tests/mochitest/value/test_number.html
@@ -0,0 +1,59 @@
+<html>
+
+<head>
+ <title>nsIAccessible value testing for input@type=range element</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../value.js"></script>
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/chrome-harness.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ // HTML5 number element tests
+ testValue("number", "", 0, 0, 0, 1);
+ testValue("number_value", "1", 1, 0, 0, 1);
+ testValue("number_step", "", 0, 0, 0, 1);
+ testValue("number_min42", "", 0, 42, 0, 1);
+ testValue("number_max42", "", 0, 0, 42, 1);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=559761"
+ title="make HTML5 input@type=number element accessible">
+ Bug 559761
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+
+ <!-- HTML5 input@type=number element -->
+ <input type="number" id="number">
+ <input type="number" id="number_value" value="1">
+ <input type="number" id="number_step" step="1">
+ <input type="number" id="number_min42" min="42">
+ <input type="number" id="number_max42" max="42">
+</body>
+</html>
diff --git a/accessible/tests/mochitest/value/test_progress.html b/accessible/tests/mochitest/value/test_progress.html
new file mode 100644
index 000000000..b99cce14c
--- /dev/null
+++ b/accessible/tests/mochitest/value/test_progress.html
@@ -0,0 +1,61 @@
+<html>
+
+<head>
+ <title>nsIAccessible value testing for progress element</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../value.js"></script>
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/chrome-harness.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ // HTML5 progress element tests
+ testValue("pr_indeterminate", "", 0, 0, 1, 0);
+ testValue("pr_zero", "0%", 0, 0, 1, 0);
+ testValue("pr_zeropointfive", "50%", 0.5, 0, 1, 0);
+ testValue("pr_one", "100%", 1, 0, 1, 0);
+ testValue("pr_42", "100%", 42, 0, 1, 0);
+ testValue("pr_21", "50%", 21, 0, 42, 0);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=559773"
+ title="make HTML5 progress element accessible">
+ Mozilla Bug 559773
+ </a><br />
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+
+ <!-- HTML5 progress element -->
+ <progress id="pr_indeterminate">this will be read by legacy browsers</progress>
+ <progress id="pr_zero" value="0">this will be read by legacy browsers</progress>
+ <progress id="pr_zeropointfive" value="0.5">this will be read by legacy browsers</progress>
+ <progress id="pr_one" value="1">this will be read by legacy browsers</progress>
+ <progress id="pr_42" value="42">this will be read by legacy browsers</progress>
+ <progress id="pr_21" value="21" max="42">this will be read by legacy browsers</progress>
+</body>
+</html>
diff --git a/accessible/tests/mochitest/value/test_progress.xul b/accessible/tests/mochitest/value/test_progress.xul
new file mode 100644
index 000000000..5ae4a358f
--- /dev/null
+++ b/accessible/tests/mochitest/value/test_progress.xul
@@ -0,0 +1,72 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="XUL progressmeter tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../value.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function doTest()
+ {
+ // progressmeter
+ testValue("pm1", "50%", 50, 0, 100, 0);
+ testValue("pm2", "50%", 500, 0, 1000, 0);
+ testValue("pm3", "", 0, 0, 100, 0);
+
+ // scale
+ testValue("sc1", "500", 500, 0, 1000, 10);
+ testValue("sc2", "", 0, 0, 0, 0);
+
+ // aria progressbar
+ testValue("ariapb1", "500", 500, 0, 1000, 0);
+ testValue("ariapb2", "", 0, 0, 0, 0);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=489551"
+ title="Values of sliders and progress bars in HTML 5 audio and video element's control sets are not percentages">
+ Mozilla Bug 489551
+ </a><br/>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <!-- progressmeter -->
+ <progressmeter id="pm1" value="50"/>
+ <progressmeter id="pm2" value="500" max="1000"/>
+ <progressmeter id="pm3"/>
+
+ <!-- scale -->
+ <scale id="sc1" value="500" max="1000" increment="10"/>
+ <scale id="sc2"/>
+
+ <!-- aria -->
+ <description id="ariapb1" role="progressbar"
+ aria-valuenow="500" aria-valuemin="0" aria-valuemax="1000"/>
+ <description id="ariapb2" role="progressbar"/>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/value/test_range.html b/accessible/tests/mochitest/value/test_range.html
new file mode 100644
index 000000000..b795f7a79
--- /dev/null
+++ b/accessible/tests/mochitest/value/test_range.html
@@ -0,0 +1,59 @@
+<html>
+
+<head>
+ <title>nsIAccessible value testing for input@type=range element</title>
+
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script type="application/javascript"
+ src="../common.js"></script>
+ <script type="application/javascript"
+ src="../value.js"></script>
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/chrome-harness.js"></script>
+
+ <script type="application/javascript">
+ function doTest()
+ {
+ // HTML5 progress element tests
+ testValue("range", "50", 50, 0, 100, 1);
+ testValue("range_value", "1", 1, 0, 100, 1);
+ testValue("range_step", "50", 50, 0, 100, 1);
+ testValue("range_min42", "71", 71, 42, 100, 1);
+ testValue("range_max42", "21", 21, 0, 42, 1);
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ </script>
+
+</head>
+
+<body>
+
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=559764"
+ title="make HTML5 input@type=range element accessible">
+ Bug 559764
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+
+ <!-- HTML5 input@type=range element -->
+ <input type="range" id="range">
+ <input type="range" id="range_value" value="1">
+ <input type="range" id="range_step" step="1">
+ <input type="range" id="range_min42" min="42">
+ <input type="range" id="range_max42" max="42">
+</body>
+</html>
diff --git a/accessible/windows/ProxyWrappers.h b/accessible/windows/ProxyWrappers.h
new file mode 100644
index 000000000..6e7c84e18
--- /dev/null
+++ b/accessible/windows/ProxyWrappers.h
@@ -0,0 +1,92 @@
+/* -*- 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_ProxyWrappers_h
+#define MOZILLA_A11Y_ProxyWrappers_h
+
+#include "HyperTextAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+class ProxyAccessibleWrap : public AccessibleWrap
+{
+public:
+ ProxyAccessibleWrap(ProxyAccessible* aProxy) :
+ AccessibleWrap(nullptr, nullptr)
+ {
+ mType = eProxyType;
+ mBits.proxy = aProxy;
+ }
+
+ virtual void Shutdown() override
+ {
+ mBits.proxy = nullptr;
+ mStateFlags |= eIsDefunct;
+ }
+
+ virtual void GetNativeInterface(void** aOutAccessible) override
+ {
+ mBits.proxy->GetCOMInterface(aOutAccessible);
+ }
+};
+
+class HyperTextProxyAccessibleWrap : public HyperTextAccessibleWrap
+{
+public:
+ HyperTextProxyAccessibleWrap(ProxyAccessible* aProxy) :
+ HyperTextAccessibleWrap(nullptr, nullptr)
+ {
+ mType = eProxyType;
+ mBits.proxy = aProxy;
+ }
+
+ virtual void Shutdown() override
+ {
+ mBits.proxy = nullptr;
+ mStateFlags |= eIsDefunct;
+ }
+
+ virtual void GetNativeInterface(void** aOutAccessible) override
+ {
+ mBits.proxy->GetCOMInterface(aOutAccessible);
+ }
+};
+
+class DocProxyAccessibleWrap : public HyperTextProxyAccessibleWrap
+{
+public:
+ DocProxyAccessibleWrap(ProxyAccessible* aProxy) :
+ HyperTextProxyAccessibleWrap(aProxy)
+ { mGenericTypes |= eDocument; }
+
+ void AddID(uint32_t aID, AccessibleWrap* aAcc)
+ { mIDToAccessibleMap.Put(aID, aAcc); }
+ void RemoveID(uint32_t aID) { mIDToAccessibleMap.Remove(aID); }
+ AccessibleWrap* GetAccessibleByID(uint32_t aID) const
+ { return mIDToAccessibleMap.Get(aID); }
+
+private:
+ /*
+ * This provides a mapping from 32 bit id to accessible objects.
+ */
+ nsDataHashtable<nsUint32HashKey, AccessibleWrap*> mIDToAccessibleMap;
+};
+
+template<typename T>
+inline ProxyAccessible*
+HyperTextProxyFor(T* aWrapper)
+{
+ static_assert(mozilla::IsBaseOf<IUnknown, T>::value, "only IAccessible* should be passed in");
+ auto wrapper = static_cast<HyperTextProxyAccessibleWrap*>(aWrapper);
+ return wrapper->IsProxy() ? wrapper->Proxy() : nullptr;
+}
+
+}
+}
+
+#endif
diff --git a/accessible/windows/ia2/ia2Accessible.cpp b/accessible/windows/ia2/ia2Accessible.cpp
new file mode 100644
index 000000000..c72719b51
--- /dev/null
+++ b/accessible/windows/ia2/ia2Accessible.cpp
@@ -0,0 +1,810 @@
+/* -*- 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 "Accessible2_i.c"
+#include "Accessible2_2_i.c"
+#include "Accessible2_3_i.c"
+#include "AccessibleRole.h"
+#include "AccessibleStates.h"
+
+#include "Compatibility.h"
+#include "ia2AccessibleRelation.h"
+#include "IUnknownImpl.h"
+#include "nsCoreUtils.h"
+#include "nsIAccessibleTypes.h"
+#include "mozilla/a11y/PDocAccessible.h"
+#include "Relation.h"
+#include "TextRange-inl.h"
+#include "nsAccessibilityService.h"
+
+#include "nsIPersistentProperties2.h"
+#include "nsISimpleEnumerator.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+template<typename String> static void EscapeAttributeChars(String& aStr);
+
+////////////////////////////////////////////////////////////////////////////////
+// ia2Accessible
+////////////////////////////////////////////////////////////////////////////////
+
+STDMETHODIMP
+ia2Accessible::QueryInterface(REFIID iid, void** ppv)
+{
+ if (!ppv)
+ return E_INVALIDARG;
+
+ *ppv = nullptr;
+
+ if (IID_IAccessible2_3 == iid)
+ *ppv = static_cast<IAccessible2_3*>(this);
+ else if (IID_IAccessible2_2 == iid)
+ *ppv = static_cast<IAccessible2_2*>(this);
+ else if (IID_IAccessible2 == iid && !Compatibility::IsIA2Off())
+ *ppv = static_cast<IAccessible2*>(this);
+
+ if (*ppv) {
+ (reinterpret_cast<IUnknown*>(*ppv))->AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// IAccessible2
+
+STDMETHODIMP
+ia2Accessible::get_nRelations(long* aNRelations)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aNRelations)
+ return E_INVALIDARG;
+ *aNRelations = 0;
+
+ AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+ if (acc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ MOZ_ASSERT(!acc->IsProxy());
+
+ for (uint32_t idx = 0; idx < ArrayLength(sRelationTypePairs); idx++) {
+ if (sRelationTypePairs[idx].second == IA2_RELATION_NULL)
+ continue;
+
+ Relation rel = acc->RelationByType(sRelationTypePairs[idx].first);
+ if (rel.Next())
+ (*aNRelations)++;
+ }
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2Accessible::get_relation(long aRelationIndex,
+ IAccessibleRelation** aRelation)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aRelation || aRelationIndex < 0)
+ return E_INVALIDARG;
+ *aRelation = nullptr;
+
+ AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+ if (acc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ MOZ_ASSERT(!acc->IsProxy());
+
+ long relIdx = 0;
+ for (uint32_t idx = 0; idx < ArrayLength(sRelationTypePairs); idx++) {
+ if (sRelationTypePairs[idx].second == IA2_RELATION_NULL)
+ continue;
+
+ RelationType relationType = sRelationTypePairs[idx].first;
+ Relation rel = acc->RelationByType(relationType);
+ RefPtr<ia2AccessibleRelation> ia2Relation =
+ new ia2AccessibleRelation(relationType, &rel);
+ if (ia2Relation->HasTargets()) {
+ if (relIdx == aRelationIndex) {
+ ia2Relation.forget(aRelation);
+ return S_OK;
+ }
+
+ relIdx++;
+ }
+ }
+
+ return E_INVALIDARG;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2Accessible::get_relations(long aMaxRelations,
+ IAccessibleRelation** aRelation,
+ long *aNRelations)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aRelation || !aNRelations || aMaxRelations <= 0)
+ return E_INVALIDARG;
+ *aNRelations = 0;
+
+ AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+ if (acc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ MOZ_ASSERT(!acc->IsProxy());
+
+ for (uint32_t idx = 0; idx < ArrayLength(sRelationTypePairs) &&
+ *aNRelations < aMaxRelations; idx++) {
+ if (sRelationTypePairs[idx].second == IA2_RELATION_NULL)
+ continue;
+
+ RelationType relationType = sRelationTypePairs[idx].first;
+ Relation rel = acc->RelationByType(relationType);
+ RefPtr<ia2AccessibleRelation> ia2Rel =
+ new ia2AccessibleRelation(relationType, &rel);
+ if (ia2Rel->HasTargets()) {
+ ia2Rel.forget(aRelation + (*aNRelations));
+ (*aNRelations)++;
+ }
+ }
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2Accessible::role(long* aRole)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aRole)
+ return E_INVALIDARG;
+ *aRole = 0;
+
+ AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+ if (acc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+#define ROLE(_geckoRole, stringRole, atkRole, macRole, \
+ msaaRole, ia2Role, nameRule) \
+ case roles::_geckoRole: \
+ *aRole = ia2Role; \
+ break;
+
+ a11y::role geckoRole;
+ MOZ_ASSERT(!acc->IsProxy());
+ geckoRole = acc->Role();
+ switch (geckoRole) {
+#include "RoleMap.h"
+ default:
+ MOZ_CRASH("Unknown role.");
+ }
+
+#undef ROLE
+
+ // Special case, if there is a ROLE_ROW inside of a ROLE_TREE_TABLE, then call
+ // the IA2 role a ROLE_OUTLINEITEM.
+ MOZ_ASSERT(!acc->IsProxy());
+ if (geckoRole == roles::ROW) {
+ Accessible* xpParent = acc->Parent();
+ if (xpParent && xpParent->Role() == roles::TREE_TABLE)
+ *aRole = ROLE_SYSTEM_OUTLINEITEM;
+ }
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2Accessible::scrollTo(enum IA2ScrollType aScrollType)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+ if (acc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ MOZ_ASSERT(!acc->IsProxy());
+ nsCoreUtils::ScrollTo(acc->Document()->PresShell(), acc->GetContent(),
+ aScrollType);
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2Accessible::scrollToPoint(enum IA2CoordinateType aCoordType,
+ long aX, long aY)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+ if (acc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ uint32_t geckoCoordType = (aCoordType == IA2_COORDTYPE_SCREEN_RELATIVE) ?
+ nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE :
+ nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE;
+
+ MOZ_ASSERT(!acc->IsProxy());
+ acc->ScrollToPoint(geckoCoordType, aX, aY);
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2Accessible::get_groupPosition(long* aGroupLevel,
+ long* aSimilarItemsInGroup,
+ long* aPositionInGroup)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aGroupLevel || !aSimilarItemsInGroup || !aPositionInGroup)
+ return E_INVALIDARG;
+
+ *aGroupLevel = 0;
+ *aSimilarItemsInGroup = 0;
+ *aPositionInGroup = 0;
+
+ AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+ if (acc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ GroupPos groupPos = acc->GroupPosition();
+
+ // Group information for accessibles having level only (like html headings
+ // elements) isn't exposed by this method. AT should look for 'level' object
+ // attribute.
+ if (!groupPos.setSize && !groupPos.posInSet)
+ return S_FALSE;
+
+ *aGroupLevel = groupPos.level;
+ *aSimilarItemsInGroup = groupPos.setSize;
+ *aPositionInGroup = groupPos.posInSet;
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2Accessible::get_states(AccessibleStates* aStates)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aStates)
+ return E_INVALIDARG;
+ *aStates = 0;
+
+ // XXX: bug 344674 should come with better approach that we have here.
+
+ AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+ if (acc->IsDefunct()) {
+ *aStates = IA2_STATE_DEFUNCT;
+ return S_OK;
+ }
+
+ uint64_t state;
+ MOZ_ASSERT(!acc->IsProxy());
+ state = acc->State();
+
+ if (state & states::INVALID)
+ *aStates |= IA2_STATE_INVALID_ENTRY;
+ if (state & states::REQUIRED)
+ *aStates |= IA2_STATE_REQUIRED;
+
+ // The following IA2 states are not supported by Gecko
+ // IA2_STATE_ARMED
+ // IA2_STATE_MANAGES_DESCENDANTS
+ // IA2_STATE_ICONIFIED
+ // IA2_STATE_INVALID // This is not a state, it is the absence of a state
+
+ if (state & states::ACTIVE)
+ *aStates |= IA2_STATE_ACTIVE;
+ if (state & states::DEFUNCT)
+ *aStates |= IA2_STATE_DEFUNCT;
+ if (state & states::EDITABLE)
+ *aStates |= IA2_STATE_EDITABLE;
+ if (state & states::HORIZONTAL)
+ *aStates |= IA2_STATE_HORIZONTAL;
+ if (state & states::MODAL)
+ *aStates |= IA2_STATE_MODAL;
+ if (state & states::MULTI_LINE)
+ *aStates |= IA2_STATE_MULTI_LINE;
+ if (state & states::OPAQUE1)
+ *aStates |= IA2_STATE_OPAQUE;
+ if (state & states::SELECTABLE_TEXT)
+ *aStates |= IA2_STATE_SELECTABLE_TEXT;
+ if (state & states::SINGLE_LINE)
+ *aStates |= IA2_STATE_SINGLE_LINE;
+ if (state & states::STALE)
+ *aStates |= IA2_STATE_STALE;
+ if (state & states::SUPPORTS_AUTOCOMPLETION)
+ *aStates |= IA2_STATE_SUPPORTS_AUTOCOMPLETION;
+ if (state & states::TRANSIENT)
+ *aStates |= IA2_STATE_TRANSIENT;
+ if (state & states::VERTICAL)
+ *aStates |= IA2_STATE_VERTICAL;
+ if (state & states::CHECKED)
+ *aStates |= IA2_STATE_CHECKABLE;
+ if (state & states::PINNED)
+ *aStates |= IA2_STATE_PINNED;
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2Accessible::get_extendedRole(BSTR* aExtendedRole)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aExtendedRole)
+ return E_INVALIDARG;
+
+ *aExtendedRole = nullptr;
+ return E_NOTIMPL;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2Accessible::get_localizedExtendedRole(BSTR* aLocalizedExtendedRole)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aLocalizedExtendedRole)
+ return E_INVALIDARG;
+
+ *aLocalizedExtendedRole = nullptr;
+ return E_NOTIMPL;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2Accessible::get_nExtendedStates(long* aNExtendedStates)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aNExtendedStates)
+ return E_INVALIDARG;
+
+ *aNExtendedStates = 0;
+ return E_NOTIMPL;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2Accessible::get_extendedStates(long aMaxExtendedStates,
+ BSTR** aExtendedStates,
+ long* aNExtendedStates)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aExtendedStates || !aNExtendedStates)
+ return E_INVALIDARG;
+
+ *aExtendedStates = nullptr;
+ *aNExtendedStates = 0;
+ return E_NOTIMPL;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2Accessible::get_localizedExtendedStates(long aMaxLocalizedExtendedStates,
+ BSTR** aLocalizedExtendedStates,
+ long* aNLocalizedExtendedStates)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aLocalizedExtendedStates || !aNLocalizedExtendedStates)
+ return E_INVALIDARG;
+
+ *aLocalizedExtendedStates = nullptr;
+ *aNLocalizedExtendedStates = 0;
+ return E_NOTIMPL;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2Accessible::get_uniqueID(long* aUniqueID)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aUniqueID)
+ return E_INVALIDARG;
+
+ AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+ *aUniqueID = AccessibleWrap::GetChildIDFor(acc);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2Accessible::get_windowHandle(HWND* aWindowHandle)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aWindowHandle)
+ return E_INVALIDARG;
+ *aWindowHandle = 0;
+
+ AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+ if (acc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ *aWindowHandle = AccessibleWrap::GetHWNDFor(acc);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2Accessible::get_indexInParent(long* aIndexInParent)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aIndexInParent)
+ return E_INVALIDARG;
+ *aIndexInParent = -1;
+
+ AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+ if (acc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ MOZ_ASSERT(!acc->IsProxy());
+ *aIndexInParent = acc->IndexInParent();
+
+ if (*aIndexInParent == -1)
+ return S_FALSE;
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2Accessible::get_locale(IA2Locale* aLocale)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aLocale)
+ return E_INVALIDARG;
+
+ // Language codes consist of a primary code and a possibly empty series of
+ // subcodes: language-code = primary-code ( "-" subcode )*
+ // Two-letter primary codes are reserved for [ISO639] language abbreviations.
+ // Any two-letter subcode is understood to be a [ISO3166] country code.
+
+ AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+ if (acc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsAutoString lang;
+ acc->Language(lang);
+
+ // If primary code consists from two letters then expose it as language.
+ int32_t offset = lang.FindChar('-', 0);
+ if (offset == -1) {
+ if (lang.Length() == 2) {
+ aLocale->language = ::SysAllocString(lang.get());
+ return S_OK;
+ }
+ } else if (offset == 2) {
+ aLocale->language = ::SysAllocStringLen(lang.get(), 2);
+
+ // If the first subcode consists from two letters then expose it as
+ // country.
+ offset = lang.FindChar('-', 3);
+ if (offset == -1) {
+ if (lang.Length() == 5) {
+ aLocale->country = ::SysAllocString(lang.get() + 3);
+ return S_OK;
+ }
+ } else if (offset == 5) {
+ aLocale->country = ::SysAllocStringLen(lang.get() + 3, 2);
+ }
+ }
+
+ // Expose as a string if primary code or subcode cannot point to language or
+ // country abbreviations or if there are more than one subcode.
+ aLocale->variant = ::SysAllocString(lang.get());
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2Accessible::get_attributes(BSTR* aAttributes)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aAttributes)
+ return E_INVALIDARG;
+ *aAttributes = nullptr;
+
+ AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+ if (acc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ // The format is name:value;name:value; with \ for escaping these
+ // characters ":;=,\".
+ if (!acc->IsProxy()) {
+ nsCOMPtr<nsIPersistentProperties> attributes = acc->Attributes();
+ return ConvertToIA2Attributes(attributes, aAttributes);
+ }
+
+ MOZ_ASSERT(!acc->IsProxy());
+ return E_UNEXPECTED;
+
+ A11Y_TRYBLOCK_END
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// IAccessible2_2
+
+STDMETHODIMP
+ia2Accessible::get_attribute(BSTR name, VARIANT* aAttribute)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aAttribute)
+ return E_INVALIDARG;
+
+ return E_NOTIMPL;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2Accessible::get_accessibleWithCaret(IUnknown** aAccessible,
+ long* aCaretOffset)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aAccessible || !aCaretOffset)
+ return E_INVALIDARG;
+
+ *aAccessible = nullptr;
+ *aCaretOffset = -1;
+
+ AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+ if (acc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ int32_t caretOffset = -1;
+ Accessible* accWithCaret = SelectionMgr()->AccessibleWithCaret(&caretOffset);
+ if (acc->Document() != accWithCaret->Document())
+ return S_FALSE;
+
+ Accessible* child = accWithCaret;
+ while (!child->IsDoc() && child != acc)
+ child = child->Parent();
+
+ if (child != acc)
+ return S_FALSE;
+
+ *aAccessible = static_cast<IAccessible2*>(
+ static_cast<AccessibleWrap*>(accWithCaret));
+ (*aAccessible)->AddRef();
+ *aCaretOffset = caretOffset;
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2Accessible::get_relationTargetsOfType(BSTR aType,
+ long aMaxTargets,
+ IUnknown*** aTargets,
+ long* aNTargets)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aTargets || !aNTargets || aMaxTargets < 0)
+ return E_INVALIDARG;
+ *aNTargets = 0;
+
+ Maybe<RelationType> relationType;
+ for (uint32_t idx = 0; idx < ArrayLength(sRelationTypePairs); idx++) {
+ if (wcscmp(aType, sRelationTypePairs[idx].second) == 0) {
+ relationType.emplace(sRelationTypePairs[idx].first);
+ break;
+ }
+ }
+ if (!relationType)
+ return E_INVALIDARG;
+
+ AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+ if (acc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsTArray<Accessible*> targets;
+ MOZ_ASSERT(!acc->IsProxy());
+ Relation rel = acc->RelationByType(*relationType);
+ Accessible* target = nullptr;
+ while ((target = rel.Next()) &&
+ static_cast<long>(targets.Length()) <= aMaxTargets) {
+ targets.AppendElement(target);
+ }
+
+ *aNTargets = targets.Length();
+ *aTargets = static_cast<IUnknown**>(
+ ::CoTaskMemAlloc(sizeof(IUnknown*) * *aNTargets));
+ if (!*aTargets)
+ return E_OUTOFMEMORY;
+
+ for (int32_t i = 0; i < *aNTargets; i++) {
+ AccessibleWrap* target= static_cast<AccessibleWrap*>(targets[i]);
+ (*aTargets)[i] = static_cast<IAccessible2*>(target);
+ (*aTargets)[i]->AddRef();
+ }
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2Accessible::get_selectionRanges(IA2Range** aRanges,
+ long *aNRanges)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aRanges || !aNRanges)
+ return E_INVALIDARG;
+
+ *aNRanges = 0;
+
+ AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+ if (acc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ AutoTArray<TextRange, 1> ranges;
+ acc->Document()->SelectionRanges(&ranges);
+ uint32_t len = ranges.Length();
+ for (uint32_t idx = 0; idx < len; idx++) {
+ if (!ranges[idx].Crop(acc)) {
+ ranges.RemoveElementAt(idx);
+ }
+ }
+
+ *aNRanges = ranges.Length();
+ *aRanges = static_cast<IA2Range*>(
+ ::CoTaskMemAlloc(sizeof(IA2Range) * *aNRanges));
+ if (!*aRanges)
+ return E_OUTOFMEMORY;
+
+ for (uint32_t idx = 0; idx < static_cast<uint32_t>(*aNRanges); idx++) {
+ AccessibleWrap* anchor =
+ static_cast<AccessibleWrap*>(ranges[idx].StartContainer());
+ (*aRanges)[idx].anchor = static_cast<IAccessible2*>(anchor);
+ anchor->AddRef();
+
+ (*aRanges)[idx].anchorOffset = ranges[idx].StartOffset();
+
+ AccessibleWrap* active =
+ static_cast<AccessibleWrap*>(ranges[idx].EndContainer());
+ (*aRanges)[idx].active = static_cast<IAccessible2*>(active);
+ active->AddRef();
+
+ (*aRanges)[idx].activeOffset = ranges[idx].EndOffset();
+ }
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Helpers
+
+template<typename String>
+static inline void
+EscapeAttributeChars(String& aStr)
+{
+ int32_t offset = 0;
+ static const char kCharsToEscape[] = ":;=,\\";
+ while ((offset = aStr.FindCharInSet(kCharsToEscape, offset)) != kNotFound) {
+ aStr.Insert('\\', offset);
+ offset += 2;
+ }
+}
+
+HRESULT
+ia2Accessible::ConvertToIA2Attributes(nsTArray<Attribute>* aAttributes,
+ BSTR* aIA2Attributes)
+{
+ nsString attrStr;
+ size_t attrCount = aAttributes->Length();
+ for (size_t i = 0; i < attrCount; i++) {
+ EscapeAttributeChars(aAttributes->ElementAt(i).Name());
+ EscapeAttributeChars(aAttributes->ElementAt(i).Value());
+ AppendUTF8toUTF16(aAttributes->ElementAt(i).Name(), attrStr);
+ attrStr.Append(':');
+ attrStr.Append(aAttributes->ElementAt(i).Value());
+ attrStr.Append(';');
+ }
+
+ if (attrStr.IsEmpty())
+ return S_FALSE;
+
+ *aIA2Attributes = ::SysAllocStringLen(attrStr.get(), attrStr.Length());
+ return *aIA2Attributes ? S_OK : E_OUTOFMEMORY;
+}
+
+HRESULT
+ia2Accessible::ConvertToIA2Attributes(nsIPersistentProperties* aAttributes,
+ BSTR* aIA2Attributes)
+{
+ *aIA2Attributes = nullptr;
+
+ // The format is name:value;name:value; with \ for escaping these
+ // characters ":;=,\".
+
+ if (!aAttributes)
+ return S_FALSE;
+
+ nsCOMPtr<nsISimpleEnumerator> propEnum;
+ aAttributes->Enumerate(getter_AddRefs(propEnum));
+ if (!propEnum)
+ return E_FAIL;
+
+ nsAutoString strAttrs;
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(propEnum->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> propSupports;
+ propEnum->GetNext(getter_AddRefs(propSupports));
+
+ nsCOMPtr<nsIPropertyElement> propElem(do_QueryInterface(propSupports));
+ if (!propElem)
+ return E_FAIL;
+
+ nsAutoCString name;
+ if (NS_FAILED(propElem->GetKey(name)))
+ return E_FAIL;
+
+ EscapeAttributeChars(name);
+
+ nsAutoString value;
+ if (NS_FAILED(propElem->GetValue(value)))
+ return E_FAIL;
+
+ EscapeAttributeChars(value);
+
+ AppendUTF8toUTF16(name, strAttrs);
+ strAttrs.Append(':');
+ strAttrs.Append(value);
+ strAttrs.Append(';');
+ }
+
+ if (strAttrs.IsEmpty())
+ return S_FALSE;
+
+ *aIA2Attributes = ::SysAllocStringLen(strAttrs.get(), strAttrs.Length());
+ return *aIA2Attributes ? S_OK : E_OUTOFMEMORY;
+}
diff --git a/accessible/windows/ia2/ia2Accessible.h b/accessible/windows/ia2/ia2Accessible.h
new file mode 100644
index 000000000..98ca4339b
--- /dev/null
+++ b/accessible/windows/ia2/ia2Accessible.h
@@ -0,0 +1,122 @@
+/* -*- 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_ia2Accessible_h_
+#define mozilla_a11y_ia2Accessible_h_
+
+#include "nsISupports.h"
+
+#include "Accessible2_3.h"
+
+namespace mozilla {
+namespace a11y {
+class Attribute;
+
+class ia2Accessible : public IAccessible2_3
+{
+public:
+
+ // IUnknown
+ STDMETHODIMP QueryInterface(REFIID, void**);
+
+ // IAccessible2
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nRelations(
+ /* [retval][out] */ long* nRelations);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_relation(
+ /* [in] */ long relationIndex,
+ /* [retval][out] */ IAccessibleRelation** relation);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_relations(
+ /* [in] */ long maxRelations,
+ /* [length_is][size_is][out] */ IAccessibleRelation** relation,
+ /* [retval][out] */ long* nRelations);
+
+ virtual HRESULT STDMETHODCALLTYPE role(
+ /* [retval][out] */ long* role);
+
+ virtual HRESULT STDMETHODCALLTYPE scrollTo(
+ /* [in] */ enum IA2ScrollType scrollType);
+
+ virtual HRESULT STDMETHODCALLTYPE scrollToPoint(
+ /* [in] */ enum IA2CoordinateType coordinateType,
+ /* [in] */ long x,
+ /* [in] */ long y);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_groupPosition(
+ /* [out] */ long* groupLevel,
+ /* [out] */ long* similarItemsInGroup,
+ /* [retval][out] */ long* positionInGroup);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_states(
+ /* [retval][out] */ AccessibleStates* states);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_extendedRole(
+ /* [retval][out] */ BSTR* extendedRole);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_localizedExtendedRole(
+ /* [retval][out] */ BSTR* localizedExtendedRole);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nExtendedStates(
+ /* [retval][out] */ long* nExtendedStates);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_extendedStates(
+ /* [in] */ long maxExtendedStates,
+ /* [length_is][length_is][size_is][size_is][out] */ BSTR** extendedStates,
+ /* [retval][out] */ long* nExtendedStates);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_localizedExtendedStates(
+ /* [in] */ long maxLocalizedExtendedStates,
+ /* [length_is][length_is][size_is][size_is][out] */ BSTR** localizedExtendedStates,
+ /* [retval][out] */ long* nLocalizedExtendedStates);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_uniqueID(
+ /* [retval][out] */ long* uniqueID);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_windowHandle(
+ /* [retval][out] */ HWND* windowHandle);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_indexInParent(
+ /* [retval][out] */ long* indexInParent);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_locale(
+ /* [retval][out] */ IA2Locale* locale);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_attributes(
+ /* [retval][out] */ BSTR* attributes);
+
+ // IAccessible2_2
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_attribute(
+ /* [in] */ BSTR name,
+ /* [out, retval] */ VARIANT* attribute);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_accessibleWithCaret(
+ /* [out] */ IUnknown** accessible,
+ /* [out, retval] */ long* caretOffset);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_relationTargetsOfType(
+ /* [in] */ BSTR type,
+ /* [in] */ long maxTargets,
+ /* [out, size_is(,*nTargets)] */ IUnknown*** targets,
+ /* [out, retval] */ long* nTargets
+ );
+
+ // IAccessible2_3
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_selectionRanges(
+ /* [out, size_is(,*nRanges)] */ IA2Range** ranges,
+ /* [out, retval] */ long *nRanges);
+
+ // Helper method
+ static HRESULT ConvertToIA2Attributes(nsIPersistentProperties* aAttributes,
+ BSTR* aIA2Attributes);
+ static HRESULT ConvertToIA2Attributes(nsTArray<Attribute>* aAttributes,
+ BSTR* aIA2Attributes);
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/ia2/ia2AccessibleAction.cpp b/accessible/windows/ia2/ia2AccessibleAction.cpp
new file mode 100644
index 000000000..0710b753f
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleAction.cpp
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 "ia2AccessibleAction.h"
+
+#include "AccessibleAction_i.c"
+
+#include "AccessibleWrap.h"
+#include "IUnknownImpl.h"
+
+using namespace mozilla::a11y;
+
+// IUnknown
+
+STDMETHODIMP
+ia2AccessibleAction::QueryInterface(REFIID iid, void** ppv)
+{
+ if (!ppv)
+ return E_INVALIDARG;
+
+ *ppv = nullptr;
+
+ if (IID_IAccessibleAction == iid &&
+ !static_cast<AccessibleWrap*>(this)->IsProxy()) {
+ *ppv = static_cast<IAccessibleAction*>(this);
+ (reinterpret_cast<IUnknown*>(*ppv))->AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+// IAccessibleAction
+
+STDMETHODIMP
+ia2AccessibleAction::nActions(long* aActionCount)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aActionCount)
+ return E_INVALIDARG;
+
+ *aActionCount = 0;
+
+ AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+ if (acc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ *aActionCount = acc->ActionCount();
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleAction::doAction(long aActionIndex)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+ if (acc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ uint8_t index = static_cast<uint8_t>(aActionIndex);
+ return acc->DoAction(index) ? S_OK : E_INVALIDARG;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleAction::get_description(long aActionIndex, BSTR *aDescription)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aDescription)
+ return E_INVALIDARG;
+ *aDescription = nullptr;
+
+ AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+ if (acc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsAutoString description;
+ uint8_t index = static_cast<uint8_t>(aActionIndex);
+ acc->ActionDescriptionAt(index, description);
+ if (description.IsEmpty())
+ return S_FALSE;
+
+ *aDescription = ::SysAllocStringLen(description.get(),
+ description.Length());
+ return *aDescription ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleAction::get_keyBinding(long aActionIndex, long aNumMaxBinding,
+ BSTR **aKeyBinding,
+ long *aNumBinding)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aKeyBinding)
+ return E_INVALIDARG;
+ *aKeyBinding = nullptr;
+
+ if (!aNumBinding)
+ return E_INVALIDARG;
+ *aNumBinding = 0;
+
+ if (aActionIndex != 0 || aNumMaxBinding < 1)
+ return E_INVALIDARG;
+
+ AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+ if (acc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ // Expose keyboard shortcut if it's not exposed via MSAA keyboard shortcut.
+ KeyBinding keyBinding = acc->AccessKey();
+ if (keyBinding.IsEmpty())
+ return S_FALSE;
+
+ keyBinding = acc->KeyboardShortcut();
+ if (keyBinding.IsEmpty())
+ return S_FALSE;
+
+ nsAutoString keyStr;
+ keyBinding.ToString(keyStr);
+
+ *aKeyBinding = static_cast<BSTR*>(::CoTaskMemAlloc(sizeof(BSTR*)));
+ if (!*aKeyBinding)
+ return E_OUTOFMEMORY;
+
+ *(aKeyBinding[0]) = ::SysAllocStringLen(keyStr.get(), keyStr.Length());
+ if (!*(aKeyBinding[0])) {
+ ::CoTaskMemFree(*aKeyBinding);
+ return E_OUTOFMEMORY;
+ }
+
+ *aNumBinding = 1;
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleAction::get_name(long aActionIndex, BSTR *aName)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aName)
+ return E_INVALIDARG;
+
+ *aName = nullptr;
+
+ AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+ if (acc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsAutoString name;
+ uint8_t index = static_cast<uint8_t>(aActionIndex);
+ acc->ActionNameAt(index, name);
+ if (name.IsEmpty())
+ return E_INVALIDARG;
+
+ *aName = ::SysAllocStringLen(name.get(), name.Length());
+ return *aName ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleAction::get_localizedName(long aActionIndex, BSTR *aLocalizedName)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aLocalizedName)
+ return E_INVALIDARG;
+
+ *aLocalizedName = nullptr;
+ return E_NOTIMPL;
+
+ A11Y_TRYBLOCK_END
+}
diff --git a/accessible/windows/ia2/ia2AccessibleAction.h b/accessible/windows/ia2/ia2AccessibleAction.h
new file mode 100644
index 000000000..6c978ff16
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleAction.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 _ACCESSIBLE_ACTION_H
+#define _ACCESSIBLE_ACTION_H
+
+#include "nsISupports.h"
+
+#include "AccessibleAction.h"
+
+namespace mozilla {
+namespace a11y {
+
+class ia2AccessibleAction: public IAccessibleAction
+{
+public:
+
+ // IUnknown
+ STDMETHODIMP QueryInterface(REFIID, void**);
+
+ // IAccessibleAction
+ virtual HRESULT STDMETHODCALLTYPE nActions(
+ /* [retval][out] */ long *nActions);
+
+ virtual HRESULT STDMETHODCALLTYPE doAction(
+ /* [in] */ long actionIndex);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_description(
+ /* [in] */ long actionIndex,
+ /* [retval][out] */ BSTR *description);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_keyBinding(
+ /* [in] */ long actionIndex,
+ /* [in] */ long nMaxBinding,
+ /* [length_is][length_is][size_is][size_is][out] */ BSTR **keyBinding,
+ /* [retval][out] */ long *nBinding);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_name(
+ /* [in] */ long actionIndex,
+ /* [retval][out] */ BSTR *name);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_localizedName(
+ /* [in] */ long actionIndex,
+ /* [retval][out] */ BSTR *localizedName);
+
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#define FORWARD_IACCESSIBLEACTION(Class) \
+virtual HRESULT STDMETHODCALLTYPE nActions(long *nActions) \
+{ \
+ return Class::nActions(nActions); \
+} \
+ \
+virtual HRESULT STDMETHODCALLTYPE doAction(long actionIndex) \
+{ \
+ return Class::doAction(actionIndex); \
+} \
+ \
+virtual HRESULT STDMETHODCALLTYPE get_description(long actionIndex, \
+ BSTR *description) \
+{ \
+ return Class::get_description(actionIndex, description); \
+} \
+ \
+virtual HRESULT STDMETHODCALLTYPE get_keyBinding(long actionIndex, \
+ long nMaxBinding, \
+ BSTR **keyBinding, \
+ long *nBinding) \
+{ \
+ return Class::get_keyBinding(actionIndex, nMaxBinding, keyBinding, nBinding);\
+} \
+ \
+virtual HRESULT STDMETHODCALLTYPE get_name(long actionIndex, BSTR *name) \
+{ \
+ return Class::get_name(actionIndex, name); \
+} \
+ \
+virtual HRESULT STDMETHODCALLTYPE get_localizedName(long actionIndex, \
+ BSTR *localizedName) \
+{ \
+ return Class::get_localizedName(actionIndex, localizedName); \
+} \
+ \
+
+#endif
+
diff --git a/accessible/windows/ia2/ia2AccessibleComponent.cpp b/accessible/windows/ia2/ia2AccessibleComponent.cpp
new file mode 100644
index 000000000..f32c09d1e
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleComponent.cpp
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 "ia2AccessibleComponent.h"
+
+#include "AccessibleComponent_i.c"
+
+#include "AccessibleWrap.h"
+#include "States.h"
+#include "IUnknownImpl.h"
+
+#include "nsIFrame.h"
+
+using namespace mozilla::a11y;
+
+// IUnknown
+
+STDMETHODIMP
+ia2AccessibleComponent::QueryInterface(REFIID iid, void** ppv)
+{
+ if (!ppv)
+ return E_INVALIDARG;
+
+ *ppv = nullptr;
+
+ if (IID_IAccessibleComponent == iid) {
+ *ppv = static_cast<IAccessibleComponent*>(this);
+ (reinterpret_cast<IUnknown*>(*ppv))->AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+// IAccessibleComponent
+
+STDMETHODIMP
+ia2AccessibleComponent::get_locationInParent(long* aX, long* aY)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aX || !aY)
+ return E_INVALIDARG;
+
+ *aX = 0;
+ *aY = 0;
+
+ AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+ if (acc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ // If the object is not on any screen the returned position is (0,0).
+ uint64_t state = acc->State();
+ if (state & states::INVISIBLE)
+ return S_OK;
+
+ nsIntRect rect = acc->Bounds();
+
+ // The coordinates of the returned position are relative to this object's
+ // parent or relative to the screen on which this object is rendered if it
+ // has no parent.
+ if (!acc->Parent()) {
+ *aX = rect.x;
+ *aY = rect.y;
+ return S_OK;
+ }
+
+ // The coordinates of the bounding box are given relative to the parent's
+ // coordinate system.
+ nsIntRect parentRect = acc->Parent()->Bounds();
+ *aX = rect.x - parentRect.x;
+ *aY = rect.y - parentRect.y;
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleComponent::get_foreground(IA2Color* aForeground)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aForeground)
+ return E_INVALIDARG;
+
+ *aForeground = 0;
+
+ AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+ if (acc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsIFrame* frame = acc->GetFrame();
+ if (frame)
+ *aForeground = frame->StyleColor()->mColor;
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleComponent::get_background(IA2Color* aBackground)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aBackground)
+ return E_INVALIDARG;
+
+ *aBackground = 0;
+
+ AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+ if (acc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsIFrame* frame = acc->GetFrame();
+ if (frame)
+ *aBackground = frame->StyleBackground()->mBackgroundColor;
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
diff --git a/accessible/windows/ia2/ia2AccessibleComponent.h b/accessible/windows/ia2/ia2AccessibleComponent.h
new file mode 100644
index 000000000..c14d7a113
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleComponent.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 IA2_ACCESSIBLE_COMPONENT_H_
+#define IA2_ACCESSIBLE_COMPONENT_H_
+
+#include "AccessibleComponent.h"
+
+namespace mozilla {
+namespace a11y {
+
+class ia2AccessibleComponent : public IAccessibleComponent
+{
+public:
+
+ // IUnknown
+ STDMETHODIMP QueryInterface(REFIID, void**);
+
+ // IAccessibleComponent
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_locationInParent(
+ /* [out] */ long *x,
+ /* [retval][out] */ long *y);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_foreground(
+ /* [retval][out] */ IA2Color *foreground);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_background(
+ /* [retval][out] */ IA2Color *background);
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/ia2/ia2AccessibleEditableText.cpp b/accessible/windows/ia2/ia2AccessibleEditableText.cpp
new file mode 100644
index 000000000..361d6a130
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleEditableText.cpp
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 "ia2AccessibleEditableText.h"
+
+#include "AccessibleEditableText_i.c"
+#include "HyperTextAccessible-inl.h"
+#include "HyperTextAccessibleWrap.h"
+#include "ProxyWrappers.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+using namespace mozilla::a11y;
+
+// IAccessibleEditableText
+
+STDMETHODIMP
+ia2AccessibleEditableText::copyText(long aStartOffset, long aEndOffset)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+
+ HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
+ if (textAcc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ if (!textAcc->IsValidRange(aStartOffset, aEndOffset))
+ return E_INVALIDARG;
+
+ textAcc->CopyText(aStartOffset, aEndOffset);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleEditableText::deleteText(long aStartOffset, long aEndOffset)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+
+ HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
+ if (textAcc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ if (!textAcc->IsValidRange(aStartOffset, aEndOffset))
+ return E_INVALIDARG;
+
+ textAcc->DeleteText(aStartOffset, aEndOffset);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleEditableText::insertText(long aOffset, BSTR *aText)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ uint32_t length = ::SysStringLen(*aText);
+ nsAutoString text(*aText, length);
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+
+ HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
+ if (textAcc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ if (!textAcc->IsValidOffset(aOffset))
+ return E_INVALIDARG;
+
+ textAcc->InsertText(text, aOffset);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleEditableText::cutText(long aStartOffset, long aEndOffset)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+
+ HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
+ if (textAcc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ if (!textAcc->IsValidRange(aStartOffset, aEndOffset))
+ return E_INVALIDARG;
+
+ textAcc->CutText(aStartOffset, aEndOffset);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleEditableText::pasteText(long aOffset)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+
+ HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
+ if (textAcc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ if (!textAcc->IsValidOffset(aOffset))
+ return E_INVALIDARG;
+
+ textAcc->PasteText(aOffset);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleEditableText::replaceText(long aStartOffset, long aEndOffset,
+ BSTR *aText)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
+ if (textAcc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ if (!textAcc->IsValidRange(aStartOffset, aEndOffset))
+ return E_INVALIDARG;
+
+ textAcc->DeleteText(aStartOffset, aEndOffset);
+
+ uint32_t length = ::SysStringLen(*aText);
+ nsAutoString text(*aText, length);
+ textAcc->InsertText(text, aStartOffset);
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleEditableText::setAttributes(long aStartOffset, long aEndOffset,
+ BSTR *aAttributes)
+{
+ return E_NOTIMPL;
+}
diff --git a/accessible/windows/ia2/ia2AccessibleEditableText.h b/accessible/windows/ia2/ia2AccessibleEditableText.h
new file mode 100644
index 000000000..a1501ca51
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleEditableText.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 _ACCESSIBLE_EDITABLETEXT_H
+#define _ACCESSIBLE_EDITABLETEXT_H
+
+#include "nsISupports.h"
+
+#include "AccessibleEditableText.h"
+
+namespace mozilla {
+namespace a11y {
+
+class ia2AccessibleEditableText: public IAccessibleEditableText
+{
+public:
+
+ // IAccessibleEditableText
+ virtual HRESULT STDMETHODCALLTYPE copyText(
+ /* [in] */ long startOffset,
+ /* [in] */ long endOffset);
+
+ virtual HRESULT STDMETHODCALLTYPE deleteText(
+ /* [in] */ long startOffset,
+ /* [in] */ long endOffset);
+
+ virtual HRESULT STDMETHODCALLTYPE insertText(
+ /* [in] */ long offset,
+ /* [in] */ BSTR *text);
+
+ virtual HRESULT STDMETHODCALLTYPE cutText(
+ /* [in] */ long startOffset,
+ /* [in] */ long endOffset);
+
+ virtual HRESULT STDMETHODCALLTYPE pasteText(
+ /* [in] */ long offset);
+
+ virtual HRESULT STDMETHODCALLTYPE replaceText(
+ /* [in] */ long startOffset,
+ /* [in] */ long endOffset,
+ /* [in] */ BSTR *text);
+
+ virtual HRESULT STDMETHODCALLTYPE setAttributes(
+ /* [in] */ long startOffset,
+ /* [in] */ long endOffset,
+ /* [in] */ BSTR *attributes);
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/ia2/ia2AccessibleHyperlink.cpp b/accessible/windows/ia2/ia2AccessibleHyperlink.cpp
new file mode 100644
index 000000000..c6d564103
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleHyperlink.cpp
@@ -0,0 +1,205 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 "Accessible2.h"
+#include "AccessibleHyperlink.h"
+#include "AccessibleHyperlink_i.c"
+
+#include "AccessibleWrap.h"
+#include "IUnknownImpl.h"
+#include "nsIURI.h"
+
+using namespace mozilla::a11y;
+
+// IUnknown
+
+STDMETHODIMP
+ia2AccessibleHyperlink::QueryInterface(REFIID iid, void** ppv)
+{
+ if (!ppv)
+ return E_INVALIDARG;
+
+ *ppv = nullptr;
+
+ if (IID_IAccessibleHyperlink == iid) {
+ auto accWrap = static_cast<AccessibleWrap*>(this);
+ if (accWrap->IsProxy() ?
+ !(accWrap->ProxyInterfaces() & Interfaces::HYPERLINK) :
+ !accWrap->IsLink())
+ return E_NOINTERFACE;
+
+ *ppv = static_cast<IAccessibleHyperlink*>(this);
+ (reinterpret_cast<IUnknown*>(*ppv))->AddRef();
+ return S_OK;
+ }
+
+ return ia2AccessibleAction::QueryInterface(iid, ppv);
+}
+
+// IAccessibleHyperlink
+
+STDMETHODIMP
+ia2AccessibleHyperlink::get_anchor(long aIndex, VARIANT* aAnchor)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aAnchor)
+ return E_INVALIDARG;
+
+ VariantInit(aAnchor);
+
+ Accessible* thisObj = static_cast<AccessibleWrap*>(this);
+ MOZ_ASSERT(!thisObj->IsProxy());
+
+ if (thisObj->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ if (aIndex < 0 || aIndex >= static_cast<long>(thisObj->AnchorCount()))
+ return E_INVALIDARG;
+
+ if (!thisObj->IsLink())
+ return S_FALSE;
+
+ AccessibleWrap* anchor =
+ static_cast<AccessibleWrap*>(thisObj->AnchorAt(aIndex));
+ if (!anchor)
+ return S_FALSE;
+
+ void* instancePtr = nullptr;
+ HRESULT result = anchor->QueryInterface(IID_IUnknown, &instancePtr);
+ if (FAILED(result))
+ return result;
+
+ aAnchor->punkVal = static_cast<IUnknown*>(instancePtr);
+ aAnchor->vt = VT_UNKNOWN;
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleHyperlink::get_anchorTarget(long aIndex, VARIANT* aAnchorTarget)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aAnchorTarget) {
+ return E_INVALIDARG;
+ }
+
+ VariantInit(aAnchorTarget);
+
+ Accessible* thisObj = static_cast<AccessibleWrap*>(this);
+ nsAutoCString uriStr;
+ MOZ_ASSERT(!thisObj->IsProxy());
+ if (thisObj->IsDefunct()) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ if (aIndex < 0 || aIndex >= static_cast<long>(thisObj->AnchorCount())) {
+ return E_INVALIDARG;
+ }
+
+ if (!thisObj->IsLink()) {
+ return S_FALSE;
+ }
+
+ nsCOMPtr<nsIURI> uri = thisObj->AnchorURIAt(aIndex);
+ if (!uri) {
+ return S_FALSE;
+ }
+
+ nsresult rv = uri->GetSpec(uriStr);
+ if (NS_FAILED(rv)) {
+ return GetHRESULT(rv);
+ }
+
+ nsAutoString stringURI;
+ AppendUTF8toUTF16(uriStr, stringURI);
+
+ aAnchorTarget->vt = VT_BSTR;
+ aAnchorTarget->bstrVal = ::SysAllocStringLen(stringURI.get(),
+ stringURI.Length());
+ return aAnchorTarget->bstrVal ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleHyperlink::get_startIndex(long* aIndex)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aIndex)
+ return E_INVALIDARG;
+
+ *aIndex = 0;
+
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+
+ Accessible* thisObj = static_cast<AccessibleWrap*>(this);
+ if (thisObj->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ if (!thisObj->IsLink())
+ return S_FALSE;
+
+ *aIndex = thisObj->StartOffset();
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleHyperlink::get_endIndex(long* aIndex)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aIndex)
+ return E_INVALIDARG;
+
+ *aIndex = 0;
+
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+
+ Accessible* thisObj = static_cast<AccessibleWrap*>(this);
+ if (thisObj->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ if (!thisObj->IsLink())
+ return S_FALSE;
+
+ *aIndex = thisObj->EndOffset();
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleHyperlink::get_valid(boolean* aValid)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aValid)
+ return E_INVALIDARG;
+
+ *aValid = false;
+
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+
+ Accessible* thisObj = static_cast<AccessibleWrap*>(this);
+ if (thisObj->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ if (!thisObj->IsLink())
+ return S_FALSE;
+
+ *aValid = thisObj->IsLinkValid();
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
diff --git a/accessible/windows/ia2/ia2AccessibleHyperlink.h b/accessible/windows/ia2/ia2AccessibleHyperlink.h
new file mode 100644
index 000000000..dc34f5bc9
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleHyperlink.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 _ACCESSIBLE_HYPERLINK_H
+#define _ACCESSIBLE_HYPERLINK_H
+
+#include "nsISupports.h"
+
+#include "ia2AccessibleAction.h"
+#include "AccessibleHyperlink.h"
+
+namespace mozilla {
+namespace a11y {
+
+class ia2AccessibleHyperlink : public ia2AccessibleAction,
+ public IAccessibleHyperlink
+{
+public:
+
+ // IUnknown
+ STDMETHODIMP QueryInterface(REFIID, void**);
+
+ // IAccessibleAction
+ FORWARD_IACCESSIBLEACTION(ia2AccessibleAction)
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_anchor(
+ /* [in] */ long index,
+ /* [retval][out] */ VARIANT *anchor);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_anchorTarget(
+ /* [in] */ long index,
+ /* [retval][out] */ VARIANT *anchorTarget);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_startIndex(
+ /* [retval][out] */ long *index);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_endIndex(
+ /* [retval][out] */ long *index);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_valid(
+ /* [retval][out] */ boolean *valid);
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/ia2/ia2AccessibleHypertext.cpp b/accessible/windows/ia2/ia2AccessibleHypertext.cpp
new file mode 100644
index 000000000..f4b3bdaf2
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleHypertext.cpp
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 "ia2AccessibleHypertext.h"
+
+#include "AccessibleHypertext_i.c"
+
+#include "HyperTextAccessibleWrap.h"
+#include "IUnknownImpl.h"
+
+using namespace mozilla::a11y;
+
+// IAccessibleHypertext
+
+STDMETHODIMP
+ia2AccessibleHypertext::get_nHyperlinks(long* aHyperlinkCount)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aHyperlinkCount)
+ return E_INVALIDARG;
+
+ *aHyperlinkCount = 0;
+
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+
+ HyperTextAccessibleWrap* hyperText = static_cast<HyperTextAccessibleWrap*>(this);
+ if (hyperText->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ *aHyperlinkCount = hyperText->LinkCount();
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleHypertext::get_hyperlink(long aLinkIndex,
+ IAccessibleHyperlink** aHyperlink)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aHyperlink)
+ return E_INVALIDARG;
+
+ *aHyperlink = nullptr;
+
+ AccessibleWrap* hyperLink;
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+ HyperTextAccessibleWrap* hyperText = static_cast<HyperTextAccessibleWrap*>(this);
+ if (hyperText->IsDefunct()) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ hyperLink = static_cast<AccessibleWrap*>(hyperText->LinkAt(aLinkIndex));
+
+ if (!hyperLink)
+ return E_FAIL;
+
+ *aHyperlink =
+ static_cast<IAccessibleHyperlink*>(hyperLink);
+ (*aHyperlink)->AddRef();
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleHypertext::get_hyperlinkIndex(long aCharIndex, long* aHyperlinkIndex)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aHyperlinkIndex)
+ return E_INVALIDARG;
+
+ *aHyperlinkIndex = 0;
+
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+
+ HyperTextAccessibleWrap* hyperAcc = static_cast<HyperTextAccessibleWrap*>(this);
+ if (hyperAcc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ *aHyperlinkIndex = hyperAcc->LinkIndexAtOffset(aCharIndex);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
diff --git a/accessible/windows/ia2/ia2AccessibleHypertext.h b/accessible/windows/ia2/ia2AccessibleHypertext.h
new file mode 100644
index 000000000..af5c209b4
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleHypertext.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 _ACCESSIBLE_HYPERTEXT_H
+#define _ACCESSIBLE_HYPERTEXT_H
+
+#include "nsISupports.h"
+
+#include "ia2AccessibleText.h"
+#include "AccessibleHypertext.h"
+
+namespace mozilla {
+namespace a11y {
+
+class ia2AccessibleHypertext : public ia2AccessibleText,
+ public IAccessibleHypertext
+{
+public:
+
+ // IAccessibleText
+ FORWARD_IACCESSIBLETEXT(ia2AccessibleText)
+
+ // IAccessibleHypertext
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nHyperlinks(
+ /* [retval][out] */ long* hyperlinkCount);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_hyperlink(
+ /* [in] */ long index,
+ /* [retval][out] */ IAccessibleHyperlink** hyperlink);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_hyperlinkIndex(
+ /* [in] */ long charIndex,
+ /* [retval][out] */ long* hyperlinkIndex);
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/ia2/ia2AccessibleImage.cpp b/accessible/windows/ia2/ia2AccessibleImage.cpp
new file mode 100644
index 000000000..989fde925
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleImage.cpp
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 "ia2AccessibleImage.h"
+
+#include "AccessibleImage_i.c"
+
+#include "ImageAccessibleWrap.h"
+#include "IUnknownImpl.h"
+#include "nsIAccessibleTypes.h"
+
+#include "nsString.h"
+#include "nsIURI.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+// IUnknown
+
+STDMETHODIMP
+ia2AccessibleImage::QueryInterface(REFIID iid, void** ppv)
+{
+ if (!ppv)
+ return E_INVALIDARG;
+
+ *ppv = nullptr;
+
+ if (IID_IAccessibleImage == iid) {
+ *ppv = static_cast<IAccessibleImage*>(this);
+ (static_cast<IUnknown*>(*ppv))->AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+// IAccessibleImage
+
+STDMETHODIMP
+ia2AccessibleImage::get_description(BSTR* aDescription)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aDescription)
+ return E_INVALIDARG;
+
+ *aDescription = nullptr;
+
+ ImageAccessibleWrap* acc = static_cast<ImageAccessibleWrap*>(this);
+ if (acc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsAutoString description;
+ acc->Name(description);
+ if (description.IsEmpty())
+ return S_FALSE;
+
+ *aDescription = ::SysAllocStringLen(description.get(), description.Length());
+ return *aDescription ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleImage::get_imagePosition(enum IA2CoordinateType aCoordType,
+ long* aX,
+ long* aY)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aX || !aY)
+ return E_INVALIDARG;
+
+ *aX = 0;
+ *aY = 0;
+
+ ImageAccessibleWrap* imageAcc = static_cast<ImageAccessibleWrap*>(this);
+ if (imageAcc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ uint32_t geckoCoordType = (aCoordType == IA2_COORDTYPE_SCREEN_RELATIVE) ?
+ nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE :
+ nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE;
+
+ nsIntPoint pos = imageAcc->Position(geckoCoordType);
+ *aX = pos.x;
+ *aY = pos.y;
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleImage::get_imageSize(long* aHeight, long* aWidth)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aHeight || !aWidth)
+ return E_INVALIDARG;
+
+ *aHeight = 0;
+ *aWidth = 0;
+
+ ImageAccessibleWrap* imageAcc = static_cast<ImageAccessibleWrap*>(this);
+ if (imageAcc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsIntSize size = imageAcc->Size();
+ *aHeight = size.width;
+ *aWidth = size.height;
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
diff --git a/accessible/windows/ia2/ia2AccessibleImage.h b/accessible/windows/ia2/ia2AccessibleImage.h
new file mode 100644
index 000000000..d065cf34c
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleImage.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 _ACCESSIBLE_IMAGE_H
+#define _ACCESSIBLE_IMAGE_H
+
+#include "AccessibleImage.h"
+
+namespace mozilla {
+namespace a11y {
+
+class ia2AccessibleImage : public IAccessibleImage
+{
+public:
+
+ // IUnknown
+ STDMETHODIMP QueryInterface(REFIID, void**);
+
+ // IAccessibleImage
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_description(
+ /* [retval][out] */ BSTR *description);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_imagePosition(
+ /* [in] */ enum IA2CoordinateType coordinateType,
+ /* [out] */ long *x,
+ /* [retval][out] */ long *y);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_imageSize(
+ /* [out] */ long *height,
+ /* [retval][out] */ long *width);
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/ia2/ia2AccessibleRelation.cpp b/accessible/windows/ia2/ia2AccessibleRelation.cpp
new file mode 100644
index 000000000..cc6fd83c8
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleRelation.cpp
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 "ia2AccessibleRelation.h"
+
+#include "Relation.h"
+#include "nsID.h"
+
+#include "AccessibleRelation_i.c"
+
+using namespace mozilla::a11y;
+
+ia2AccessibleRelation::ia2AccessibleRelation(RelationType aType, Relation* aRel) :
+ mType(aType)
+{
+ Accessible* target = nullptr;
+ while ((target = aRel->Next()))
+ mTargets.AppendElement(target);
+}
+
+// IUnknown
+
+IMPL_IUNKNOWN_QUERY_HEAD(ia2AccessibleRelation)
+ IMPL_IUNKNOWN_QUERY_IFACE(IAccessibleRelation)
+ IMPL_IUNKNOWN_QUERY_IFACE(IUnknown)
+IMPL_IUNKNOWN_QUERY_TAIL
+
+// IAccessibleRelation
+
+STDMETHODIMP
+ia2AccessibleRelation::get_relationType(BSTR* aRelationType)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aRelationType)
+ return E_INVALIDARG;
+
+ *aRelationType = nullptr;
+
+#define RELATIONTYPE(geckoType, geckoTypeName, atkType, msaaType, ia2Type) \
+ case RelationType::geckoType: \
+ *aRelationType = ::SysAllocString(ia2Type); \
+ break;
+
+ switch (mType) {
+#include "RelationTypeMap.h"
+ }
+
+ return *aRelationType ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleRelation::get_localizedRelationType(BSTR *aLocalizedRelationType)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aLocalizedRelationType)
+ return E_INVALIDARG;
+
+ *aLocalizedRelationType = nullptr;
+ return E_NOTIMPL;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleRelation::get_nTargets(long *aNTargets)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aNTargets)
+ return E_INVALIDARG;
+
+ *aNTargets = mTargets.Length();
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleRelation::get_target(long aTargetIndex, IUnknown **aTarget)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (aTargetIndex < 0 || (uint32_t)aTargetIndex >= mTargets.Length() || !aTarget)
+ return E_INVALIDARG;
+
+ AccessibleWrap* target =
+ static_cast<AccessibleWrap*>(mTargets[aTargetIndex].get());
+ *aTarget = static_cast<IAccessible*>(target);
+ (*aTarget)->AddRef();
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleRelation::get_targets(long aMaxTargets, IUnknown **aTargets,
+ long *aNTargets)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aNTargets || !aTargets)
+ return E_INVALIDARG;
+
+ *aNTargets = 0;
+ long maxTargets = mTargets.Length();
+ if (maxTargets > aMaxTargets)
+ maxTargets = aMaxTargets;
+
+ for (long idx = 0; idx < maxTargets; idx++)
+ get_target(idx, aTargets + idx);
+
+ *aNTargets = maxTargets;
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
diff --git a/accessible/windows/ia2/ia2AccessibleRelation.h b/accessible/windows/ia2/ia2AccessibleRelation.h
new file mode 100644
index 000000000..faaa31cb0
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleRelation.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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_RELATION_WRAP_H
+#define _NS_ACCESSIBLE_RELATION_WRAP_H
+
+#include "Accessible.h"
+#include "IUnknownImpl.h"
+
+#include <utility>
+#include "nsTArray.h"
+
+#include "AccessibleRelation.h"
+
+namespace mozilla {
+namespace a11y {
+
+class ia2AccessibleRelation final : public IAccessibleRelation
+{
+public:
+ ia2AccessibleRelation(RelationType aType, Relation* aRel);
+
+ ia2AccessibleRelation(RelationType aType,
+ nsTArray<RefPtr<Accessible>>&& aTargets) :
+ mType(aType), mTargets(Move(aTargets)) {}
+
+ // IUnknown
+ DECL_IUNKNOWN
+
+ // IAccessibleRelation
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_relationType(
+ /* [retval][out] */ BSTR *relationType);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_localizedRelationType(
+ /* [retval][out] */ BSTR *localizedRelationType);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nTargets(
+ /* [retval][out] */ long *nTargets);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_target(
+ /* [in] */ long targetIndex,
+ /* [retval][out] */ IUnknown **target);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_targets(
+ /* [in] */ long maxTargets,
+ /* [length_is][size_is][out] */ IUnknown **target,
+ /* [retval][out] */ long *nTargets);
+
+ inline bool HasTargets() const
+ { return mTargets.Length(); }
+
+private:
+ ia2AccessibleRelation();
+ ia2AccessibleRelation(const ia2AccessibleRelation&);
+ ia2AccessibleRelation& operator = (const ia2AccessibleRelation&);
+
+ RelationType mType;
+ nsTArray<RefPtr<Accessible> > mTargets;
+};
+
+
+/**
+ * Gecko to IAccessible2 relation types map.
+ */
+
+const WCHAR *const IA2_RELATION_NULL = L"";
+
+#define RELATIONTYPE(geckoType, name, atkType, msaaType, ia2Type) \
+ std::pair<RelationType, const WCHAR *const>(RelationType::geckoType, ia2Type),
+
+static const std::pair<RelationType, const WCHAR *const> sRelationTypePairs[] = {
+#include "RelationTypeMap.h"
+};
+
+#undef RELATIONTYPE
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/windows/ia2/ia2AccessibleTable.cpp b/accessible/windows/ia2/ia2AccessibleTable.cpp
new file mode 100644
index 000000000..6ac6bbab4
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleTable.cpp
@@ -0,0 +1,747 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 "ia2AccessibleTable.h"
+
+#include "Accessible2.h"
+#include "AccessibleTable_i.c"
+#include "AccessibleTable2_i.c"
+
+#include "AccessibleWrap.h"
+#include "IUnknownImpl.h"
+#include "Statistics.h"
+#include "TableAccessible.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+using namespace mozilla::a11y;
+
+// IUnknown
+
+STDMETHODIMP
+ia2AccessibleTable::QueryInterface(REFIID iid, void** ppv)
+{
+ if (!ppv)
+ return E_INVALIDARG;
+
+ *ppv = nullptr;
+
+ if (IID_IAccessibleTable == iid) {
+ statistics::IAccessibleTableUsed();
+ *ppv = static_cast<IAccessibleTable*>(this);
+ (reinterpret_cast<IUnknown*>(*ppv))->AddRef();
+ return S_OK;
+ }
+
+ if (IID_IAccessibleTable2 == iid) {
+ *ppv = static_cast<IAccessibleTable2*>(this);
+ (reinterpret_cast<IUnknown*>(*ppv))->AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// IAccessibleTable
+
+STDMETHODIMP
+ia2AccessibleTable::get_accessibleAt(long aRowIdx, long aColIdx,
+ IUnknown** aAccessible)
+{
+ return get_cellAt(aRowIdx, aColIdx, aAccessible);
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_caption(IUnknown** aAccessible)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aAccessible)
+ return E_INVALIDARG;
+
+ *aAccessible = nullptr;
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ AccessibleWrap* caption = static_cast<AccessibleWrap*>(mTable->Caption());
+ if (!caption)
+ return S_FALSE;
+
+ (*aAccessible = static_cast<IAccessible*>(caption))->AddRef();
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_childIndex(long aRowIdx, long aColIdx,
+ long* aChildIdx)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aChildIdx)
+ return E_INVALIDARG;
+
+ *aChildIdx = 0;
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ if (aRowIdx < 0 || aColIdx < 0 ||
+ static_cast<uint32_t>(aRowIdx) >= mTable->RowCount() ||
+ static_cast<uint32_t>(aColIdx) >= mTable->ColCount())
+ return E_INVALIDARG;
+
+ *aChildIdx = mTable->CellIndexAt(aRowIdx, aColIdx);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_columnDescription(long aColIdx, BSTR* aDescription)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aDescription)
+ return E_INVALIDARG;
+
+ *aDescription = nullptr;
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ if (aColIdx < 0 || static_cast<uint32_t>(aColIdx) >= mTable->ColCount())
+ return E_INVALIDARG;
+
+ nsAutoString descr;
+ mTable->ColDescription(aColIdx, descr);
+ if (descr.IsEmpty())
+ return S_FALSE;
+
+ *aDescription = ::SysAllocStringLen(descr.get(), descr.Length());
+ return *aDescription ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_columnExtentAt(long aRowIdx, long aColIdx,
+ long* aSpan)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aSpan)
+ return E_INVALIDARG;
+
+ *aSpan = 0;
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ if (aRowIdx < 0 || aColIdx < 0 ||
+ static_cast<uint32_t>(aRowIdx) >= mTable->RowCount() ||
+ static_cast<uint32_t>(aColIdx) >= mTable->ColCount())
+ return E_INVALIDARG;
+
+ *aSpan = mTable->ColExtentAt(aRowIdx, aColIdx);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_columnHeader(IAccessibleTable** aAccessibleTable,
+ long* aStartingRowIndex)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aAccessibleTable || !aStartingRowIndex)
+ return E_INVALIDARG;
+
+ *aAccessibleTable = nullptr;
+ *aStartingRowIndex = -1;
+ return E_NOTIMPL;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_columnIndex(long aCellIdx, long* aColIdx)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aColIdx)
+ return E_INVALIDARG;
+
+ *aColIdx = 0;
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ if (aCellIdx < 0 ||
+ static_cast<uint32_t>(aCellIdx) >= mTable->ColCount() * mTable->RowCount())
+ return E_INVALIDARG;
+
+ *aColIdx = mTable->ColIndexAt(aCellIdx);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_nColumns(long* aColCount)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aColCount)
+ return E_INVALIDARG;
+
+ *aColCount = 0;
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ *aColCount = mTable->ColCount();
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_nRows(long* aRowCount)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aRowCount)
+ return E_INVALIDARG;
+
+ *aRowCount = 0;
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ *aRowCount = mTable->RowCount();
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_nSelectedChildren(long* aChildCount)
+{
+ return get_nSelectedCells(aChildCount);
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_nSelectedColumns(long* aColCount)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aColCount)
+ return E_INVALIDARG;
+
+ *aColCount = 0;
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ *aColCount = mTable->SelectedColCount();
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_nSelectedRows(long* aRowCount)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aRowCount)
+ return E_INVALIDARG;
+
+ *aRowCount = 0;
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ *aRowCount = mTable->SelectedRowCount();
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_rowDescription(long aRowIdx, BSTR* aDescription)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aDescription)
+ return E_INVALIDARG;
+
+ *aDescription = nullptr;
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ if (aRowIdx < 0 || static_cast<uint32_t>(aRowIdx) >= mTable->RowCount())
+ return E_INVALIDARG;
+
+ nsAutoString descr;
+ mTable->RowDescription(aRowIdx, descr);
+ if (descr.IsEmpty())
+ return S_FALSE;
+
+ *aDescription = ::SysAllocStringLen(descr.get(), descr.Length());
+ return *aDescription ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_rowExtentAt(long aRowIdx, long aColIdx, long* aSpan)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aSpan)
+ return E_INVALIDARG;
+
+ *aSpan = 0;
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ if (aRowIdx < 0 || aColIdx < 0 ||
+ static_cast<uint32_t>(aRowIdx) >= mTable->RowCount() ||
+ static_cast<uint32_t>(aColIdx) >= mTable->ColCount())
+ return E_INVALIDARG;
+
+ *aSpan = mTable->RowExtentAt(aRowIdx, aColIdx);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_rowHeader(IAccessibleTable** aAccessibleTable,
+ long* aStartingColumnIndex)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aAccessibleTable || !aStartingColumnIndex)
+ return E_INVALIDARG;
+
+ *aAccessibleTable = nullptr;
+ *aStartingColumnIndex = -1;
+ return E_NOTIMPL;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_rowIndex(long aCellIdx, long* aRowIdx)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aRowIdx)
+ return E_INVALIDARG;
+
+ *aRowIdx = 0;
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ if (aCellIdx < 0 ||
+ static_cast<uint32_t>(aCellIdx) >= mTable->ColCount() * mTable->RowCount())
+ return E_INVALIDARG;
+
+ *aRowIdx = mTable->RowIndexAt(aCellIdx);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_selectedChildren(long aMaxChildren, long** aChildren,
+ long* aNChildren)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aChildren || !aNChildren)
+ return E_INVALIDARG;
+
+ *aChildren = nullptr;
+ *aNChildren = 0;
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ AutoTArray<uint32_t, 30> cellIndices;
+ mTable->SelectedCellIndices(&cellIndices);
+
+ uint32_t maxCells = cellIndices.Length();
+ if (maxCells == 0)
+ return S_FALSE;
+
+ *aChildren = static_cast<LONG*>(moz_xmalloc(sizeof(LONG) * maxCells));
+ *aNChildren = maxCells;
+ for (uint32_t i = 0; i < maxCells; i++)
+ (*aChildren)[i] = cellIndices[i];
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_selectedColumns(long aMaxColumns, long** aColumns,
+ long* aNColumns)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ return get_selectedColumns(aColumns, aNColumns);
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_selectedRows(long aMaxRows, long** aRows, long* aNRows)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ return get_selectedRows(aRows, aNRows);
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_summary(IUnknown** aAccessible)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aAccessible)
+ return E_INVALIDARG;
+
+ // 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 Table::summary to get a summary as a string which is not mapped
+ // directly to IAccessible2.
+
+ *aAccessible = nullptr;
+ return S_FALSE;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_isColumnSelected(long aColIdx, boolean* aIsSelected)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aIsSelected)
+ return E_INVALIDARG;
+
+ *aIsSelected = false;
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ if (aColIdx < 0 || static_cast<uint32_t>(aColIdx) >= mTable->ColCount())
+ return E_INVALIDARG;
+
+ *aIsSelected = mTable->IsColSelected(aColIdx);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_isRowSelected(long aRowIdx, boolean* aIsSelected)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aIsSelected)
+ return E_INVALIDARG;
+
+ *aIsSelected = false;
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ if (aRowIdx < 0 || static_cast<uint32_t>(aRowIdx) >= mTable->RowCount())
+ return E_INVALIDARG;
+
+ *aIsSelected = mTable->IsRowSelected(aRowIdx);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_isSelected(long aRowIdx, long aColIdx,
+ boolean* aIsSelected)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aIsSelected)
+ return E_INVALIDARG;
+
+ *aIsSelected = false;
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ if (aRowIdx < 0 || aColIdx < 0 ||
+ static_cast<uint32_t>(aColIdx) >= mTable->ColCount() ||
+ static_cast<uint32_t>(aRowIdx) >= mTable->RowCount())
+ return E_INVALIDARG;
+
+ *aIsSelected = mTable->IsCellSelected(aRowIdx, aColIdx);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::selectRow(long aRowIdx)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ if (aRowIdx < 0 || static_cast<uint32_t>(aRowIdx) >= mTable->RowCount())
+ return E_INVALIDARG;
+
+ mTable->SelectRow(aRowIdx);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::selectColumn(long aColIdx)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ if (aColIdx < 0 || static_cast<uint32_t>(aColIdx) >= mTable->ColCount())
+ return E_INVALIDARG;
+
+ mTable->SelectCol(aColIdx);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::unselectRow(long aRowIdx)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ if (aRowIdx < 0 || static_cast<uint32_t>(aRowIdx) >= mTable->RowCount())
+ return E_INVALIDARG;
+
+ mTable->UnselectRow(aRowIdx);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::unselectColumn(long aColIdx)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ if (aColIdx < 0 || static_cast<uint32_t>(aColIdx) >= mTable->ColCount())
+ return E_INVALIDARG;
+
+ mTable->UnselectCol(aColIdx);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_rowColumnExtentsAtIndex(long aCellIdx, long* aRowIdx,
+ long* aColIdx,
+ long* aRowExtents,
+ long* aColExtents,
+ boolean* aIsSelected)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aRowIdx || !aColIdx || !aRowExtents || !aColExtents || !aIsSelected)
+ return E_INVALIDARG;
+
+ *aRowIdx = 0;
+ *aColIdx = 0;
+ *aRowExtents = 0;
+ *aColExtents = 0;
+ *aIsSelected = false;
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ if (aCellIdx < 0 ||
+ static_cast<uint32_t>(aCellIdx) >= mTable->ColCount() * mTable->RowCount())
+ return E_INVALIDARG;
+
+ int32_t colIdx = 0, rowIdx = 0;
+ mTable->RowAndColIndicesAt(aCellIdx, &rowIdx, &colIdx);
+ *aRowIdx = rowIdx;
+ *aColIdx = colIdx;
+ *aRowExtents = mTable->RowExtentAt(rowIdx, colIdx);
+ *aColExtents = mTable->ColExtentAt(rowIdx, colIdx);
+ *aIsSelected = mTable->IsCellSelected(rowIdx, colIdx);
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_modelChange(IA2TableModelChange* aModelChange)
+{
+ return E_NOTIMPL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// IAccessibleTable2
+
+STDMETHODIMP
+ia2AccessibleTable::get_cellAt(long aRowIdx, long aColIdx, IUnknown** aCell)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aCell)
+ return E_INVALIDARG;
+
+ *aCell = nullptr;
+
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ AccessibleWrap* cell =
+ static_cast<AccessibleWrap*>(mTable->CellAt(aRowIdx, aColIdx));
+ if (!cell)
+ return E_INVALIDARG;
+
+ (*aCell = static_cast<IAccessible*>(cell))->AddRef();
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_nSelectedCells(long* aCellCount)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aCellCount)
+ return E_INVALIDARG;
+
+ *aCellCount = 0;
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ *aCellCount = mTable->SelectedCellCount();
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_selectedCells(IUnknown*** aCells, long* aNSelectedCells)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aCells || !aNSelectedCells)
+ return E_INVALIDARG;
+
+ *aCells = nullptr;
+ *aNSelectedCells = 0;
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ AutoTArray<Accessible*, 30> cells;
+ mTable->SelectedCells(&cells);
+ if (cells.IsEmpty())
+ return S_FALSE;
+
+ *aCells =
+ static_cast<IUnknown**>(::CoTaskMemAlloc(sizeof(IUnknown*) *
+ cells.Length()));
+ if (!*aCells)
+ return E_OUTOFMEMORY;
+
+ for (uint32_t i = 0; i < cells.Length(); i++) {
+ (*aCells)[i] =
+ static_cast<IAccessible*>(static_cast<AccessibleWrap*>(cells[i]));
+ ((*aCells)[i])->AddRef();
+ }
+
+ *aNSelectedCells = cells.Length();
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_selectedColumns(long** aColumns, long* aNColumns)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aColumns || !aNColumns)
+ return E_INVALIDARG;
+
+ *aColumns = nullptr;
+ *aNColumns = 0;
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ AutoTArray<uint32_t, 30> colIndices;
+ mTable->SelectedColIndices(&colIndices);
+
+ uint32_t maxCols = colIndices.Length();
+ if (maxCols == 0)
+ return S_FALSE;
+
+ *aColumns = static_cast<LONG*>(moz_xmalloc(sizeof(LONG) * maxCols));
+ *aNColumns = maxCols;
+ for (uint32_t i = 0; i < maxCols; i++)
+ (*aColumns)[i] = colIndices[i];
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTable::get_selectedRows(long** aRows, long* aNRows)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aRows || !aNRows)
+ return E_INVALIDARG;
+
+ *aRows = nullptr;
+ *aNRows = 0;
+ if (!mTable)
+ return CO_E_OBJNOTCONNECTED;
+
+ AutoTArray<uint32_t, 30> rowIndices;
+ mTable->SelectedRowIndices(&rowIndices);
+
+ uint32_t maxRows = rowIndices.Length();
+ if (maxRows == 0)
+ return S_FALSE;
+
+ *aRows = static_cast<LONG*>(moz_xmalloc(sizeof(LONG) * maxRows));
+ *aNRows = maxRows;
+ for (uint32_t i = 0; i < maxRows; i++)
+ (*aRows)[i] = rowIndices[i];
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
diff --git a/accessible/windows/ia2/ia2AccessibleTable.h b/accessible/windows/ia2/ia2AccessibleTable.h
new file mode 100644
index 000000000..6738e2dfc
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleTable.h
@@ -0,0 +1,176 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 _ACCESSIBLE_TABLE_H
+#define _ACCESSIBLE_TABLE_H
+
+#include "AccessibleTable.h"
+#include "AccessibleTable2.h"
+
+namespace mozilla {
+namespace a11y {
+
+class TableAccessible;
+
+class ia2AccessibleTable : public IAccessibleTable,
+ public IAccessibleTable2
+{
+public:
+
+ // IUnknown
+ STDMETHODIMP QueryInterface(REFIID, void**);
+
+ // IAccessibleTable
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_accessibleAt(
+ /* [in] */ long row,
+ /* [in] */ long column,
+ /* [retval][out] */ IUnknown **accessible);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_caption(
+ /* [retval][out] */ IUnknown **accessible);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_childIndex(
+ /* [in] */ long rowIndex,
+ /* [in] */ long columnIndex,
+ /* [retval][out] */ long *childIndex);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_columnDescription(
+ /* [in] */ long column,
+ /* [retval][out] */ BSTR *description);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_columnExtentAt(
+ /* [in] */ long row,
+ /* [in] */ long column,
+ /* [retval][out] */ long *nColumnsSpanned);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_columnHeader(
+ /* [out] */ IAccessibleTable **accessibleTable,
+ /* [retval][out] */ long *startingRowIndex);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_columnIndex(
+ /* [in] */ long childIndex,
+ /* [retval][out] */ long *columnIndex);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nColumns(
+ /* [retval][out] */ long *columnCount);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nRows(
+ /* [retval][out] */ long *rowCount);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nSelectedChildren(
+ /* [retval][out] */ long *childCount);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nSelectedColumns(
+ /* [retval][out] */ long *columnCount);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nSelectedRows(
+ /* [retval][out] */ long *rowCount);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_rowDescription(
+ /* [in] */ long row,
+ /* [retval][out] */ BSTR *description);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_rowExtentAt(
+ /* [in] */ long row,
+ /* [in] */ long column,
+ /* [retval][out] */ long *nRowsSpanned);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_rowHeader(
+ /* [out] */ IAccessibleTable **accessibleTable,
+ /* [retval][out] */ long *startingColumnIndex);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_rowIndex(
+ /* [in] */ long childIndex,
+ /* [retval][out] */ long *rowIndex);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_selectedChildren(
+ /* [in] */ long maxChildren,
+ /* [length_is][length_is][size_is][size_is][out] */ long **children,
+ /* [retval][out] */ long *nChildren);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_selectedColumns(
+ /* [in] */ long maxColumns,
+ /* [length_is][length_is][size_is][size_is][out] */ long **columns,
+ /* [retval][out] */ long *nColumns);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_selectedRows(
+ /* [in] */ long maxRows,
+ /* [length_is][length_is][size_is][size_is][out] */ long **rows,
+ /* [retval][out] */ long *nRows);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_summary(
+ /* [retval][out] */ IUnknown **accessible);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_isColumnSelected(
+ /* [in] */ long column,
+ /* [retval][out] */ boolean *isSelected);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_isRowSelected(
+ /* [in] */ long row,
+ /* [retval][out] */ boolean *isSelected);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_isSelected(
+ /* [in] */ long row,
+ /* [in] */ long column,
+ /* [retval][out] */ boolean *isSelected);
+
+ virtual HRESULT STDMETHODCALLTYPE selectRow(
+ /* [in] */ long row);
+
+ virtual HRESULT STDMETHODCALLTYPE selectColumn(
+ /* [in] */ long column);
+
+ virtual HRESULT STDMETHODCALLTYPE unselectRow(
+ /* [in] */ long row);
+
+ virtual HRESULT STDMETHODCALLTYPE unselectColumn(
+ /* [in] */ long column);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_rowColumnExtentsAtIndex(
+ /* [in] */ long index,
+ /* [out] */ long *row,
+ /* [out] */ long *column,
+ /* [out] */ long *rowExtents,
+ /* [out] */ long *columnExtents,
+ /* [retval][out] */ boolean *isSelected);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_modelChange(
+ /* [retval][out] */ IA2TableModelChange *modelChange);
+
+
+ // IAccessibleTable2
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_cellAt(
+ /* [in] */ long row,
+ /* [in] */ long column,
+ /* [out, retval] */ IUnknown **cell);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nSelectedCells(
+ /* [out, retval] */ long *cellCount);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_selectedCells(
+ /* [out, size_is(,*nSelectedCells,)] */ IUnknown ***cells,
+ /* [out, retval] */ long *nSelectedCells);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_selectedColumns(
+ /* [out, size_is(,*nColumns)] */ long **selectedColumns,
+ /* [out, retval] */ long *nColumns);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_selectedRows(
+ /* [out, size_is(,*nRows)] */ long **selectedRows,
+ /* [out, retval] */ long *nRows);
+
+protected:
+ ia2AccessibleTable(TableAccessible* aTable) : mTable(aTable) {}
+
+ TableAccessible* mTable;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/ia2/ia2AccessibleTableCell.cpp b/accessible/windows/ia2/ia2AccessibleTableCell.cpp
new file mode 100644
index 000000000..e625a7049
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleTableCell.cpp
@@ -0,0 +1,257 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 "ia2AccessibleTableCell.h"
+
+#include "Accessible2.h"
+#include "AccessibleTable2_i.c"
+#include "AccessibleTableCell_i.c"
+
+#include "AccessibleWrap.h"
+#include "TableAccessible.h"
+#include "TableCellAccessible.h"
+#include "IUnknownImpl.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+using namespace mozilla::a11y;
+
+// IUnknown
+
+STDMETHODIMP
+ia2AccessibleTableCell::QueryInterface(REFIID iid, void** ppv)
+{
+ if (!ppv)
+ return E_INVALIDARG;
+
+ *ppv = nullptr;
+
+ if (IID_IAccessibleTableCell == iid) {
+ *ppv = static_cast<IAccessibleTableCell*>(this);
+ (reinterpret_cast<IUnknown*>(*ppv))->AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// IAccessibleTableCell
+
+STDMETHODIMP
+ia2AccessibleTableCell::get_table(IUnknown** aTable)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aTable)
+ return E_INVALIDARG;
+
+ *aTable = nullptr;
+ if (!mTableCell)
+ return CO_E_OBJNOTCONNECTED;
+
+ TableAccessible* table = mTableCell->Table();
+ if (!table)
+ return E_FAIL;
+
+ AccessibleWrap* wrap = static_cast<AccessibleWrap*>(table->AsAccessible());
+ *aTable = static_cast<IAccessible*>(wrap);
+ (*aTable)->AddRef();
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTableCell::get_columnExtent(long* aSpan)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aSpan)
+ return E_INVALIDARG;
+
+ *aSpan = 0;
+ if (!mTableCell)
+ return CO_E_OBJNOTCONNECTED;
+
+ *aSpan = mTableCell->ColExtent();
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTableCell::get_columnHeaderCells(IUnknown*** aCellAccessibles,
+ long* aNColumnHeaderCells)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aCellAccessibles || !aNColumnHeaderCells)
+ return E_INVALIDARG;
+
+ *aCellAccessibles = nullptr;
+ *aNColumnHeaderCells = 0;
+ if (!mTableCell)
+ return CO_E_OBJNOTCONNECTED;
+
+ AutoTArray<Accessible*, 10> cells;
+ mTableCell->ColHeaderCells(&cells);
+
+ *aNColumnHeaderCells = cells.Length();
+ *aCellAccessibles =
+ static_cast<IUnknown**>(::CoTaskMemAlloc(sizeof(IUnknown*) *
+ cells.Length()));
+
+ if (!*aCellAccessibles)
+ return E_OUTOFMEMORY;
+
+ for (uint32_t i = 0; i < cells.Length(); i++) {
+ AccessibleWrap* cell = static_cast<AccessibleWrap*>(cells[i]);
+ (*aCellAccessibles)[i] = static_cast<IAccessible*>(cell);
+ (*aCellAccessibles)[i]->AddRef();
+ }
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTableCell::get_columnIndex(long* aColIdx)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aColIdx)
+ return E_INVALIDARG;
+
+ *aColIdx = -1;
+ if (!mTableCell)
+ return CO_E_OBJNOTCONNECTED;
+
+ *aColIdx = mTableCell->ColIdx();
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTableCell::get_rowExtent(long* aSpan)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aSpan)
+ return E_INVALIDARG;
+
+ *aSpan = 0;
+ if (!mTableCell)
+ return CO_E_OBJNOTCONNECTED;
+
+ *aSpan = mTableCell->RowExtent();
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTableCell::get_rowHeaderCells(IUnknown*** aCellAccessibles,
+ long* aNRowHeaderCells)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aCellAccessibles || !aNRowHeaderCells)
+ return E_INVALIDARG;
+
+ *aCellAccessibles = nullptr;
+ *aNRowHeaderCells = 0;
+ if (!mTableCell)
+ return CO_E_OBJNOTCONNECTED;
+
+ AutoTArray<Accessible*, 10> cells;
+ mTableCell->RowHeaderCells(&cells);
+
+ *aNRowHeaderCells = cells.Length();
+ *aCellAccessibles =
+ static_cast<IUnknown**>(::CoTaskMemAlloc(sizeof(IUnknown*) *
+ cells.Length()));
+ if (!*aCellAccessibles)
+ return E_OUTOFMEMORY;
+
+ for (uint32_t i = 0; i < cells.Length(); i++) {
+ AccessibleWrap* cell = static_cast<AccessibleWrap*>(cells[i]);
+ (*aCellAccessibles)[i] = static_cast<IAccessible*>(cell);
+ (*aCellAccessibles)[i]->AddRef();
+ }
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTableCell::get_rowIndex(long* aRowIdx)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aRowIdx)
+ return E_INVALIDARG;
+
+ *aRowIdx = -1;
+ if (!mTableCell)
+ return CO_E_OBJNOTCONNECTED;
+
+ *aRowIdx = mTableCell->RowIdx();
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTableCell::get_rowColumnExtents(long* aRowIdx, long* aColIdx,
+ long* aRowExtents,
+ long* aColExtents,
+ boolean* aIsSelected)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aRowIdx || !aColIdx || !aRowExtents || !aColExtents || !aIsSelected)
+ return E_INVALIDARG;
+
+ *aRowIdx = *aColIdx = *aRowExtents = *aColExtents = 0;
+ *aIsSelected = false;
+ if (!mTableCell)
+ return CO_E_OBJNOTCONNECTED;
+
+ *aRowIdx = mTableCell->RowIdx();
+ *aColIdx = mTableCell->ColIdx();
+ *aRowExtents = mTableCell->RowExtent();
+ *aColExtents = mTableCell->ColExtent();
+ *aIsSelected = mTableCell->Selected();
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleTableCell::get_isSelected(boolean* aIsSelected)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aIsSelected)
+ return E_INVALIDARG;
+
+ *aIsSelected = false;
+ if (!mTableCell)
+ return CO_E_OBJNOTCONNECTED;
+
+ *aIsSelected = mTableCell->Selected();
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
diff --git a/accessible/windows/ia2/ia2AccessibleTableCell.h b/accessible/windows/ia2/ia2AccessibleTableCell.h
new file mode 100644
index 000000000..883ca2875
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleTableCell.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 _ACCESSIBLE_TABLECELL_H
+#define _ACCESSIBLE_TABLECELL_H
+
+#include "AccessibleTableCell.h"
+
+namespace mozilla {
+namespace a11y {
+class TableCellAccessible;
+
+class ia2AccessibleTableCell : public IAccessibleTableCell
+{
+public:
+
+ // IUnknown
+ STDMETHODIMP QueryInterface(REFIID, void**);
+
+ // IAccessibleTableCell
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_table(
+ /* [out, retval] */ IUnknown **table);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_columnExtent(
+ /* [out, retval] */ long *nColumnsSpanned);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_columnHeaderCells(
+ /* [out, size_is(,*nColumnHeaderCells,)] */ IUnknown ***cellAccessibles,
+ /* [out, retval] */ long *nColumnHeaderCells);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_columnIndex(
+ /* [out, retval] */ long *columnIndex);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_rowExtent(
+ /* [out, retval] */ long *nRowsSpanned);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_rowHeaderCells(
+ /* [out, size_is(,*nRowHeaderCells,)] */ IUnknown ***cellAccessibles,
+ /* [out, retval] */ long *nRowHeaderCells);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_rowIndex(
+ /* [out, retval] */ long *rowIndex);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_rowColumnExtents(
+ /* [out] */ long *row,
+ /* [out] */ long *column,
+ /* [out] */ long *rowExtents,
+ /* [out] */ long *columnExtents,
+ /* [out, retval] */ boolean *isSelected);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_isSelected(
+ /* [out, retval] */ boolean *isSelected);
+
+protected:
+ ia2AccessibleTableCell(TableCellAccessible* aTableCell) :
+ mTableCell(aTableCell) {}
+
+ TableCellAccessible* mTableCell;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/ia2/ia2AccessibleText.cpp b/accessible/windows/ia2/ia2AccessibleText.cpp
new file mode 100644
index 000000000..7ac766f30
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleText.cpp
@@ -0,0 +1,598 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 "ia2AccessibleText.h"
+
+#include "Accessible2.h"
+#include "AccessibleText_i.c"
+
+#include "HyperTextAccessibleWrap.h"
+#include "HyperTextAccessible-inl.h"
+#include "ProxyWrappers.h"
+#include "mozilla/ClearOnShutdown.h"
+
+using namespace mozilla::a11y;
+
+StaticRefPtr<HyperTextAccessibleWrap> ia2AccessibleText::sLastTextChangeAcc;
+StaticAutoPtr<nsString> ia2AccessibleText::sLastTextChangeString;
+uint32_t ia2AccessibleText::sLastTextChangeStart = 0;
+uint32_t ia2AccessibleText::sLastTextChangeEnd = 0;
+bool ia2AccessibleText::sLastTextChangeWasInsert = false;
+
+// IAccessibleText
+
+STDMETHODIMP
+ia2AccessibleText::addSelection(long aStartOffset, long aEndOffset)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+
+ HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
+ if (textAcc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ return textAcc->AddToSelection(aStartOffset, aEndOffset) ?
+ S_OK : E_INVALIDARG;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleText::get_attributes(long aOffset, long *aStartOffset,
+ long *aEndOffset, BSTR *aTextAttributes)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aStartOffset || !aEndOffset || !aTextAttributes)
+ return E_INVALIDARG;
+
+ *aStartOffset = 0;
+ *aEndOffset = 0;
+ *aTextAttributes = nullptr;
+
+ int32_t startOffset = 0, endOffset = 0;
+ HRESULT hr;
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+ HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
+ if (textAcc->IsDefunct()) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ textAcc->TextAttributes(true, aOffset, &startOffset, &endOffset);
+
+ hr = AccessibleWrap::ConvertToIA2Attributes(attributes, aTextAttributes);
+ if (FAILED(hr))
+ return hr;
+
+ *aStartOffset = startOffset;
+ *aEndOffset = endOffset;
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleText::get_caretOffset(long *aOffset)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aOffset)
+ return E_INVALIDARG;
+
+ *aOffset = -1;
+
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+ HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
+ if (textAcc->IsDefunct()) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ *aOffset = textAcc->CaretOffset();
+
+ return *aOffset != -1 ? S_OK : S_FALSE;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleText::get_characterExtents(long aOffset,
+ enum IA2CoordinateType aCoordType,
+ long* aX, long* aY,
+ long* aWidth, long* aHeight)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aX || !aY || !aWidth || !aHeight)
+ return E_INVALIDARG;
+ *aX = *aY = *aWidth = *aHeight = 0;
+
+ uint32_t geckoCoordType = (aCoordType == IA2_COORDTYPE_SCREEN_RELATIVE) ?
+ nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE :
+ nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE;
+ nsIntRect rect;
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+ HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
+ if (textAcc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ rect = textAcc->CharBounds(aOffset, geckoCoordType);
+
+ *aX = rect.x;
+ *aY = rect.y;
+ *aWidth = rect.width;
+ *aHeight = rect.height;
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleText::get_nSelections(long* aNSelections)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aNSelections)
+ return E_INVALIDARG;
+ *aNSelections = 0;
+
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+ HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
+ if (textAcc->IsDefunct()) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ *aNSelections = textAcc->SelectionCount();
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleText::get_offsetAtPoint(long aX, long aY,
+ enum IA2CoordinateType aCoordType,
+ long* aOffset)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aOffset)
+ return E_INVALIDARG;
+ *aOffset = 0;
+
+ uint32_t geckoCoordType = (aCoordType == IA2_COORDTYPE_SCREEN_RELATIVE) ?
+ nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE :
+ nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE;
+
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+ HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
+ if (textAcc->IsDefunct()) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ *aOffset = textAcc->OffsetAtPoint(aX, aY, geckoCoordType);
+
+ return *aOffset == -1 ? S_FALSE : S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleText::get_selection(long aSelectionIndex, long* aStartOffset,
+ long* aEndOffset)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aStartOffset || !aEndOffset)
+ return E_INVALIDARG;
+ *aStartOffset = *aEndOffset = 0;
+
+ int32_t startOffset = 0, endOffset = 0;
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+ HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
+ if (textAcc->IsDefunct()) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ if (!textAcc->SelectionBoundsAt(aSelectionIndex, &startOffset, &endOffset)) {
+ return E_INVALIDARG;
+ }
+
+ *aStartOffset = startOffset;
+ *aEndOffset = endOffset;
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleText::get_text(long aStartOffset, long aEndOffset, BSTR* aText)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aText)
+ return E_INVALIDARG;
+
+ *aText = nullptr;
+
+ nsAutoString text;
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+ HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
+ if (textAcc->IsDefunct()) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ if (!textAcc->IsValidRange(aStartOffset, aEndOffset)) {
+ return E_INVALIDARG;
+ }
+
+ textAcc->TextSubstring(aStartOffset, aEndOffset, text);
+
+ if (text.IsEmpty())
+ return S_FALSE;
+
+ *aText = ::SysAllocStringLen(text.get(), text.Length());
+ return *aText ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleText::get_textBeforeOffset(long aOffset,
+ enum IA2TextBoundaryType aBoundaryType,
+ long* aStartOffset, long* aEndOffset,
+ BSTR* aText)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aStartOffset || !aEndOffset || !aText)
+ return E_INVALIDARG;
+
+ *aStartOffset = *aEndOffset = 0;
+ *aText = nullptr;
+
+ HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
+ if (textAcc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ if (!textAcc->IsValidOffset(aOffset))
+ return E_INVALIDARG;
+
+ nsAutoString text;
+ int32_t startOffset = 0, endOffset = 0;
+
+ if (aBoundaryType == IA2_TEXT_BOUNDARY_ALL) {
+ startOffset = 0;
+ endOffset = textAcc->CharacterCount();
+ textAcc->TextSubstring(startOffset, endOffset, text);
+ } else {
+ AccessibleTextBoundary boundaryType = GetGeckoTextBoundary(aBoundaryType);
+ if (boundaryType == -1)
+ return S_FALSE;
+
+ textAcc->TextBeforeOffset(aOffset, boundaryType, &startOffset, &endOffset, text);
+ }
+
+ *aStartOffset = startOffset;
+ *aEndOffset = endOffset;
+
+ if (text.IsEmpty())
+ return S_FALSE;
+
+ *aText = ::SysAllocStringLen(text.get(), text.Length());
+ return *aText ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleText::get_textAfterOffset(long aOffset,
+ enum IA2TextBoundaryType aBoundaryType,
+ long* aStartOffset, long* aEndOffset,
+ BSTR* aText)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aStartOffset || !aEndOffset || !aText)
+ return E_INVALIDARG;
+
+ *aStartOffset = 0;
+ *aEndOffset = 0;
+ *aText = nullptr;
+
+ HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
+ if (textAcc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ if (!textAcc->IsValidOffset(aOffset))
+ return E_INVALIDARG;
+
+ nsAutoString text;
+ int32_t startOffset = 0, endOffset = 0;
+
+ if (aBoundaryType == IA2_TEXT_BOUNDARY_ALL) {
+ startOffset = 0;
+ endOffset = textAcc->CharacterCount();
+ textAcc->TextSubstring(startOffset, endOffset, text);
+ } else {
+ AccessibleTextBoundary boundaryType = GetGeckoTextBoundary(aBoundaryType);
+ if (boundaryType == -1)
+ return S_FALSE;
+ textAcc->TextAfterOffset(aOffset, boundaryType, &startOffset, &endOffset, text);
+ }
+
+ *aStartOffset = startOffset;
+ *aEndOffset = endOffset;
+
+ if (text.IsEmpty())
+ return S_FALSE;
+
+ *aText = ::SysAllocStringLen(text.get(), text.Length());
+ return *aText ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleText::get_textAtOffset(long aOffset,
+ enum IA2TextBoundaryType aBoundaryType,
+ long* aStartOffset, long* aEndOffset,
+ BSTR* aText)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aStartOffset || !aEndOffset || !aText)
+ return E_INVALIDARG;
+
+ *aStartOffset = *aEndOffset = 0;
+ *aText = nullptr;
+
+ HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
+ if (textAcc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ if (!textAcc->IsValidOffset(aOffset))
+ return E_INVALIDARG;
+
+ nsAutoString text;
+ int32_t startOffset = 0, endOffset = 0;
+ if (aBoundaryType == IA2_TEXT_BOUNDARY_ALL) {
+ startOffset = 0;
+ endOffset = textAcc->CharacterCount();
+ textAcc->TextSubstring(startOffset, endOffset, text);
+ } else {
+ AccessibleTextBoundary boundaryType = GetGeckoTextBoundary(aBoundaryType);
+ if (boundaryType == -1)
+ return S_FALSE;
+ textAcc->TextAtOffset(aOffset, boundaryType, &startOffset, &endOffset, text);
+ }
+
+ *aStartOffset = startOffset;
+ *aEndOffset = endOffset;
+
+ if (text.IsEmpty())
+ return S_FALSE;
+
+ *aText = ::SysAllocStringLen(text.get(), text.Length());
+ return *aText ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleText::removeSelection(long aSelectionIndex)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+
+ HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
+ if (textAcc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ return textAcc->RemoveFromSelection(aSelectionIndex) ?
+ S_OK : E_INVALIDARG;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleText::setCaretOffset(long aOffset)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+
+ HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
+ if (textAcc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ if (!textAcc->IsValidOffset(aOffset))
+ return E_INVALIDARG;
+
+ textAcc->SetCaretOffset(aOffset);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleText::setSelection(long aSelectionIndex, long aStartOffset,
+ long aEndOffset)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+
+ HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
+ if (textAcc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ return textAcc->SetSelectionBoundsAt(aSelectionIndex, aStartOffset, aEndOffset) ?
+ S_OK : E_INVALIDARG;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleText::get_nCharacters(long* aNCharacters)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aNCharacters)
+ return E_INVALIDARG;
+ *aNCharacters = 0;
+
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+
+ HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
+ if (textAcc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ *aNCharacters = textAcc->CharacterCount();
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleText::scrollSubstringTo(long aStartIndex, long aEndIndex,
+ enum IA2ScrollType aScrollType)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+
+ HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
+ if (textAcc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ if (!textAcc->IsValidRange(aStartIndex, aEndIndex))
+ return E_INVALIDARG;
+
+ textAcc->ScrollSubstringTo(aStartIndex, aEndIndex, aScrollType);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleText::scrollSubstringToPoint(long aStartIndex, long aEndIndex,
+ enum IA2CoordinateType aCoordType,
+ long aX, long aY)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ uint32_t geckoCoordType = (aCoordType == IA2_COORDTYPE_SCREEN_RELATIVE) ?
+ nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE :
+ nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE;
+
+ MOZ_ASSERT(!HyperTextProxyFor(this));
+
+ HyperTextAccessible* textAcc = static_cast<HyperTextAccessibleWrap*>(this);
+ if (textAcc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ if (!textAcc->IsValidRange(aStartIndex, aEndIndex))
+ return E_INVALIDARG;
+
+ textAcc->ScrollSubstringToPoint(aStartIndex, aEndIndex,
+ geckoCoordType, aX, aY);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleText::get_newText(IA2TextSegment *aNewText)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ return GetModifiedText(true, aNewText);
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleText::get_oldText(IA2TextSegment *aOldText)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ return GetModifiedText(false, aOldText);
+
+ A11Y_TRYBLOCK_END
+}
+
+// ia2AccessibleText
+
+HRESULT
+ia2AccessibleText::GetModifiedText(bool aGetInsertedText,
+ IA2TextSegment *aText)
+{
+ if (!aText)
+ return E_INVALIDARG;
+
+ if (!sLastTextChangeAcc)
+ return S_OK;
+
+ if (aGetInsertedText != sLastTextChangeWasInsert)
+ return S_OK;
+
+ if (sLastTextChangeAcc != this)
+ return S_OK;
+
+ aText->start = sLastTextChangeStart;
+ aText->end = sLastTextChangeEnd;
+
+ if (sLastTextChangeString->IsEmpty())
+ return S_FALSE;
+
+ aText->text = ::SysAllocStringLen(sLastTextChangeString->get(), sLastTextChangeString->Length());
+ return aText->text ? S_OK : E_OUTOFMEMORY;
+}
+
+AccessibleTextBoundary
+ia2AccessibleText::GetGeckoTextBoundary(enum IA2TextBoundaryType aBoundaryType)
+{
+ switch (aBoundaryType) {
+ case IA2_TEXT_BOUNDARY_CHAR:
+ return nsIAccessibleText::BOUNDARY_CHAR;
+ case IA2_TEXT_BOUNDARY_WORD:
+ return nsIAccessibleText::BOUNDARY_WORD_START;
+ case IA2_TEXT_BOUNDARY_LINE:
+ return nsIAccessibleText::BOUNDARY_LINE_START;
+ //case IA2_TEXT_BOUNDARY_SENTENCE:
+ //case IA2_TEXT_BOUNDARY_PARAGRAPH:
+ // XXX: not implemented
+ default:
+ return -1;
+ }
+}
+
+void
+ia2AccessibleText::InitTextChangeData()
+{
+ ClearOnShutdown(&sLastTextChangeAcc);
+ ClearOnShutdown(&sLastTextChangeString);
+}
+
+void
+ia2AccessibleText::UpdateTextChangeData(HyperTextAccessibleWrap* aAcc,
+ bool aInsert, const nsString& aStr,
+ int32_t aStart, uint32_t aLen)
+{
+ if (!sLastTextChangeString)
+ sLastTextChangeString = new nsString();
+
+ sLastTextChangeAcc = aAcc;
+ sLastTextChangeStart = aStart;
+ sLastTextChangeEnd = aStart + aLen;
+ sLastTextChangeWasInsert = aInsert;
+ *sLastTextChangeString = aStr;
+}
diff --git a/accessible/windows/ia2/ia2AccessibleText.h b/accessible/windows/ia2/ia2AccessibleText.h
new file mode 100644
index 000000000..a513e44a2
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleText.h
@@ -0,0 +1,275 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 _ACCESSIBLE_TEXT_H
+#define _ACCESSIBLE_TEXT_H
+
+#include "nsIAccessibleText.h"
+
+#include "AccessibleText.h"
+
+namespace mozilla {
+namespace a11y {
+class HyperTextAccessibleWrap;
+
+class ia2AccessibleText: public IAccessibleText
+{
+public:
+
+ // IAccessibleText
+ virtual HRESULT STDMETHODCALLTYPE addSelection(
+ /* [in] */ long startOffset,
+ /* [in] */ long endOffset);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_attributes(
+ /* [in] */ long offset,
+ /* [out] */ long *startOffset,
+ /* [out] */ long *endOffset,
+ /* [retval][out] */ BSTR *textAttributes);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_caretOffset(
+ /* [retval][out] */ long *offset);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_characterExtents(
+ /* [in] */ long offset,
+ /* [in] */ enum IA2CoordinateType coordType,
+ /* [out] */ long *x,
+ /* [out] */ long *y,
+ /* [out] */ long *width,
+ /* [retval][out] */ long *height);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nSelections(
+ /* [retval][out] */ long *nSelections);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_offsetAtPoint(
+ /* [in] */ long x,
+ /* [in] */ long y,
+ /* [in] */ enum IA2CoordinateType coordType,
+ /* [retval][out] */ long *offset);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_selection(
+ /* [in] */ long selectionIndex,
+ /* [out] */ long *startOffset,
+ /* [retval][out] */ long *endOffset);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_text(
+ /* [in] */ long startOffset,
+ /* [in] */ long endOffset,
+ /* [retval][out] */ BSTR *text);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_textBeforeOffset(
+ /* [in] */ long offset,
+ /* [in] */ enum IA2TextBoundaryType boundaryType,
+ /* [out] */ long *startOffset,
+ /* [out] */ long *endOffset,
+ /* [retval][out] */ BSTR *text);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_textAfterOffset(
+ /* [in] */ long offset,
+ /* [in] */ enum IA2TextBoundaryType boundaryType,
+ /* [out] */ long *startOffset,
+ /* [out] */ long *endOffset,
+ /* [retval][out] */ BSTR *text);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_textAtOffset(
+ /* [in] */ long offset,
+ /* [in] */ enum IA2TextBoundaryType boundaryType,
+ /* [out] */ long *startOffset,
+ /* [out] */ long *endOffset,
+ /* [retval][out] */ BSTR *text);
+
+ virtual HRESULT STDMETHODCALLTYPE removeSelection(
+ /* [in] */ long selectionIndex);
+
+ virtual HRESULT STDMETHODCALLTYPE setCaretOffset(
+ /* [in] */ long offset);
+
+ virtual HRESULT STDMETHODCALLTYPE setSelection(
+ /* [in] */ long selectionIndex,
+ /* [in] */ long startOffset,
+ /* [in] */ long endOffset);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nCharacters(
+ /* [retval][out] */ long *nCharacters);
+
+ virtual HRESULT STDMETHODCALLTYPE scrollSubstringTo(
+ /* [in] */ long startIndex,
+ /* [in] */ long endIndex,
+ /* [in] */ enum IA2ScrollType scrollType);
+
+ virtual HRESULT STDMETHODCALLTYPE scrollSubstringToPoint(
+ /* [in] */ long startIndex,
+ /* [in] */ long endIndex,
+ /* [in] */ enum IA2CoordinateType coordinateType,
+ /* [in] */ long x,
+ /* [in] */ long y);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_newText(
+ /* [retval][out] */ IA2TextSegment *newText);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_oldText(
+ /* [retval][out] */ IA2TextSegment *oldText);
+
+ static void InitTextChangeData();
+ static void UpdateTextChangeData(HyperTextAccessibleWrap* aAcc, bool aInsert,
+ const nsString& aStr, int32_t aStart,
+ uint32_t aLen);
+
+protected:
+ static StaticRefPtr<HyperTextAccessibleWrap> sLastTextChangeAcc;
+ static StaticAutoPtr<nsString> sLastTextChangeString;
+ static bool sLastTextChangeWasInsert;
+ static uint32_t sLastTextChangeStart;
+ static uint32_t sLastTextChangeEnd;
+
+private:
+ HRESULT GetModifiedText(bool aGetInsertedText, IA2TextSegment *aNewText);
+ AccessibleTextBoundary GetGeckoTextBoundary(enum IA2TextBoundaryType coordinateType);
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+
+#define FORWARD_IACCESSIBLETEXT(Class) \
+virtual HRESULT STDMETHODCALLTYPE addSelection(long startOffset, \
+ long endOffset) \
+{ \
+ return Class::addSelection(startOffset, endOffset); \
+} \
+ \
+virtual HRESULT STDMETHODCALLTYPE get_attributes(long offset, \
+ long *startOffset, \
+ long *endOffset, \
+ BSTR *textAttributes) \
+{ \
+ return Class::get_attributes(offset, startOffset, endOffset, textAttributes);\
+} \
+ \
+virtual HRESULT STDMETHODCALLTYPE get_caretOffset(long *offset) \
+{ \
+ return Class::get_caretOffset(offset); \
+} \
+ \
+virtual HRESULT STDMETHODCALLTYPE get_characterExtents(long offset, \
+ enum IA2CoordinateType coordType,\
+ long *x, \
+ long *y, \
+ long *width, \
+ long *height) \
+{ \
+ return Class::get_characterExtents(offset, coordType, x, y, width, height); \
+} \
+ \
+virtual HRESULT STDMETHODCALLTYPE get_nSelections(long *nSelections) \
+{ \
+ return Class::get_nSelections(nSelections); \
+} \
+ \
+virtual HRESULT STDMETHODCALLTYPE get_offsetAtPoint(long x, \
+ long y, \
+ enum IA2CoordinateType coordType,\
+ long *offset) \
+{ \
+ return Class::get_offsetAtPoint(x, y, coordType, offset); \
+} \
+ \
+virtual HRESULT STDMETHODCALLTYPE get_selection(long selectionIndex, \
+ long *startOffset, \
+ long *endOffset) \
+{ \
+ return Class::get_selection(selectionIndex, startOffset, endOffset); \
+} \
+ \
+virtual HRESULT STDMETHODCALLTYPE get_text(long startOffset, \
+ long endOffset, \
+ BSTR *text) \
+{ \
+ return Class::get_text(startOffset, endOffset, text); \
+} \
+ \
+virtual HRESULT STDMETHODCALLTYPE get_textBeforeOffset(long offset, \
+ enum IA2TextBoundaryType boundaryType,\
+ long *startOffset, \
+ long *endOffset, \
+ BSTR *text) \
+{ \
+ return Class::get_textBeforeOffset(offset, boundaryType, \
+ startOffset, endOffset, text); \
+} \
+ \
+virtual HRESULT STDMETHODCALLTYPE get_textAfterOffset(long offset, \
+ enum IA2TextBoundaryType boundaryType,\
+ long *startOffset, \
+ long *endOffset, \
+ BSTR *text) \
+{ \
+ return Class::get_textAfterOffset(offset, boundaryType, \
+ startOffset, endOffset, text); \
+} \
+ \
+virtual HRESULT STDMETHODCALLTYPE get_textAtOffset(long offset, \
+ enum IA2TextBoundaryType boundaryType,\
+ long *startOffset, \
+ long *endOffset, \
+ BSTR *text) \
+{ \
+ return Class::get_textAtOffset(offset, boundaryType, \
+ startOffset, endOffset, text); \
+} \
+ \
+virtual HRESULT STDMETHODCALLTYPE removeSelection(long selectionIndex) \
+{ \
+ return Class::removeSelection(selectionIndex); \
+} \
+ \
+virtual HRESULT STDMETHODCALLTYPE setCaretOffset(long offset) \
+{ \
+ return Class::setCaretOffset(offset); \
+} \
+ \
+virtual HRESULT STDMETHODCALLTYPE setSelection(long selectionIndex, \
+ long startOffset, \
+ long endOffset) \
+{ \
+ return Class::setSelection(selectionIndex, startOffset, endOffset); \
+} \
+ \
+virtual HRESULT STDMETHODCALLTYPE get_nCharacters(long *nCharacters) \
+{ \
+ return Class::get_nCharacters(nCharacters); \
+} \
+ \
+virtual HRESULT STDMETHODCALLTYPE scrollSubstringTo(long startIndex, \
+ long endIndex, \
+ enum IA2ScrollType scrollType)\
+{ \
+ return Class::scrollSubstringTo(startIndex, endIndex, scrollType); \
+} \
+ \
+virtual HRESULT STDMETHODCALLTYPE scrollSubstringToPoint(long startIndex, \
+ long endIndex, \
+ enum IA2CoordinateType coordinateType,\
+ long x, \
+ long y) \
+{ \
+ return Class::scrollSubstringToPoint(startIndex, endIndex, \
+ coordinateType, x, y); \
+} \
+ \
+virtual HRESULT STDMETHODCALLTYPE get_newText(IA2TextSegment *newText) \
+{ \
+ return Class::get_newText(newText); \
+} \
+ \
+virtual HRESULT STDMETHODCALLTYPE get_oldText(IA2TextSegment *oldText) \
+{ \
+ return Class::get_oldText(oldText); \
+} \
+
+#endif
+
diff --git a/accessible/windows/ia2/ia2AccessibleValue.cpp b/accessible/windows/ia2/ia2AccessibleValue.cpp
new file mode 100644
index 000000000..e33442295
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleValue.cpp
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 "ia2AccessibleValue.h"
+
+#include "AccessibleValue_i.c"
+
+#include "AccessibleWrap.h"
+#include "Accessible-inl.h"
+#include "IUnknownImpl.h"
+
+#include "mozilla/FloatingPoint.h"
+
+using namespace mozilla::a11y;
+
+// IUnknown
+
+STDMETHODIMP
+ia2AccessibleValue::QueryInterface(REFIID iid, void** ppv)
+{
+ if (!ppv)
+ return E_INVALIDARG;
+
+ *ppv = nullptr;
+
+ if (IID_IAccessibleValue == iid) {
+ AccessibleWrap* valueAcc = static_cast<AccessibleWrap*>(this);
+ if (valueAcc->HasNumericValue()) {
+ *ppv = static_cast<IAccessibleValue*>(this);
+ valueAcc->AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+ }
+
+ return E_NOINTERFACE;
+}
+
+// IAccessibleValue
+
+STDMETHODIMP
+ia2AccessibleValue::get_currentValue(VARIANT* aCurrentValue)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aCurrentValue)
+ return E_INVALIDARG;
+
+ VariantInit(aCurrentValue);
+
+ AccessibleWrap* valueAcc = static_cast<AccessibleWrap*>(this);
+ double currentValue;
+ MOZ_ASSERT(!valueAcc->IsProxy());
+ if (valueAcc->IsDefunct()) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ currentValue = valueAcc->CurValue();
+
+ if (IsNaN(currentValue))
+ return S_FALSE;
+
+ aCurrentValue->vt = VT_R8;
+ aCurrentValue->dblVal = currentValue;
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleValue::setCurrentValue(VARIANT aValue)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (aValue.vt != VT_R8)
+ return E_INVALIDARG;
+
+ AccessibleWrap* valueAcc = static_cast<AccessibleWrap*>(this);
+ MOZ_ASSERT(!valueAcc->IsProxy());
+
+ if (valueAcc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ return valueAcc->SetCurValue(aValue.dblVal) ? S_OK : E_FAIL;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleValue::get_maximumValue(VARIANT* aMaximumValue)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aMaximumValue)
+ return E_INVALIDARG;
+
+ VariantInit(aMaximumValue);
+
+ AccessibleWrap* valueAcc = static_cast<AccessibleWrap*>(this);
+ double maximumValue;
+ MOZ_ASSERT(!valueAcc->IsProxy());
+ if (valueAcc->IsDefunct()) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ maximumValue = valueAcc->MaxValue();
+
+ if (IsNaN(maximumValue))
+ return S_FALSE;
+
+ aMaximumValue->vt = VT_R8;
+ aMaximumValue->dblVal = maximumValue;
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ia2AccessibleValue::get_minimumValue(VARIANT* aMinimumValue)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aMinimumValue)
+ return E_INVALIDARG;
+
+ VariantInit(aMinimumValue);
+
+ AccessibleWrap* valueAcc = static_cast<AccessibleWrap*>(this);
+ double minimumValue;
+ MOZ_ASSERT(!valueAcc->IsProxy());
+ if (valueAcc->IsDefunct()) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ minimumValue = valueAcc->MinValue();
+
+ if (IsNaN(minimumValue))
+ return S_FALSE;
+
+ aMinimumValue->vt = VT_R8;
+ aMinimumValue->dblVal = minimumValue;
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
diff --git a/accessible/windows/ia2/ia2AccessibleValue.h b/accessible/windows/ia2/ia2AccessibleValue.h
new file mode 100644
index 000000000..97ce5ea59
--- /dev/null
+++ b/accessible/windows/ia2/ia2AccessibleValue.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 _ACCESSIBLE_VALUE_H
+#define _ACCESSIBLE_VALUE_H
+
+#include "AccessibleValue.h"
+
+namespace mozilla {
+namespace a11y {
+
+class ia2AccessibleValue: public IAccessibleValue
+{
+public:
+
+ // IUnknown
+ STDMETHODIMP QueryInterface(REFIID, void**);
+
+ // IAccessibleValue
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_currentValue(
+ /* [retval][out] */ VARIANT *currentValue);
+
+ virtual HRESULT STDMETHODCALLTYPE setCurrentValue(
+ /* [in] */ VARIANT value);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_maximumValue(
+ /* [retval][out] */ VARIANT *maximumValue);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_minimumValue(
+ /* [retval][out] */ VARIANT *minimumValue);
+
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/ia2/moz.build b/accessible/windows/ia2/moz.build
new file mode 100644
index 000000000..443e87663
--- /dev/null
+++ b/accessible/windows/ia2/moz.build
@@ -0,0 +1,58 @@
+# -*- 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 += [
+ 'ia2Accessible.h',
+ 'ia2AccessibleAction.h',
+ 'ia2AccessibleComponent.h',
+ 'ia2AccessibleEditableText.h',
+ 'ia2AccessibleHyperlink.h',
+ 'ia2AccessibleHypertext.h',
+ 'ia2AccessibleText.h',
+ 'ia2AccessibleValue.h',
+]
+
+UNIFIED_SOURCES += [
+ 'ia2Accessible.cpp',
+ 'ia2AccessibleAction.cpp',
+ 'ia2AccessibleComponent.cpp',
+ 'ia2AccessibleEditableText.cpp',
+ 'ia2AccessibleHyperlink.cpp',
+ 'ia2AccessibleHypertext.cpp',
+ 'ia2AccessibleImage.cpp',
+ 'ia2AccessibleRelation.cpp',
+ 'ia2AccessibleText.cpp',
+ 'ia2AccessibleValue.cpp',
+]
+
+# These files cannot be built in unified mode because they both include
+# AccessibleTable2_i.c.
+SOURCES += [
+ 'ia2AccessibleTable.cpp',
+ 'ia2AccessibleTableCell.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/accessible/base',
+ '/accessible/generic',
+ '/accessible/html',
+ '/accessible/windows',
+ '/accessible/windows/msaa',
+ '/accessible/xpcom',
+ '/accessible/xul',
+]
+
+FINAL_LIBRARY = 'xul'
+
+# The Windows MIDL code generator creates things like:
+#
+# #endif !_MIDL_USE_GUIDDEF_
+#
+# which clang-cl complains about. MSVC doesn't, so turn this warning off.
+if CONFIG['CLANG_CL']:
+ CXXFLAGS += ['-Wno-extra-tokens']
+
+include('/ipc/chromium/chromium-config.mozbuild')
diff --git a/accessible/windows/moz.build b/accessible/windows/moz.build
new file mode 100644
index 000000000..4bfa4f330
--- /dev/null
+++ b/accessible/windows/moz.build
@@ -0,0 +1,8 @@
+# -*- 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/.
+
+DIRS += ['msaa', 'ia2', 'sdn', 'uia']
+
diff --git a/accessible/windows/msaa/ARIAGridAccessibleWrap.cpp b/accessible/windows/msaa/ARIAGridAccessibleWrap.cpp
new file mode 100644
index 000000000..04f7c112f
--- /dev/null
+++ b/accessible/windows/msaa/ARIAGridAccessibleWrap.cpp
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 "ARIAGridAccessibleWrap.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// ARIAGridAccessibleWrap
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS_INHERITED0(ARIAGridAccessibleWrap,
+ ARIAGridAccessible)
+
+IMPL_IUNKNOWN_INHERITED1(ARIAGridAccessibleWrap,
+ AccessibleWrap,
+ ia2AccessibleTable)
+
+void
+ARIAGridAccessibleWrap::Shutdown()
+{
+ ia2AccessibleTable::mTable = nullptr;
+ ARIAGridAccessible::Shutdown();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ARIAGridCellAccessibleWrap
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS_INHERITED0(ARIAGridCellAccessibleWrap,
+ ARIAGridCellAccessible)
+
+IMPL_IUNKNOWN_INHERITED1(ARIAGridCellAccessibleWrap,
+ HyperTextAccessibleWrap,
+ ia2AccessibleTableCell)
+
+void
+ARIAGridCellAccessibleWrap::Shutdown()
+{
+ ia2AccessibleTableCell::mTableCell = nullptr;
+ ARIAGridCellAccessible::Shutdown();
+}
diff --git a/accessible/windows/msaa/ARIAGridAccessibleWrap.h b/accessible/windows/msaa/ARIAGridAccessibleWrap.h
new file mode 100644
index 000000000..b03cc349f
--- /dev/null
+++ b/accessible/windows/msaa/ARIAGridAccessibleWrap.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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_ARIAGRIDACCESSIBLEWRAP_H
+#define MOZILLA_A11Y_ARIAGRIDACCESSIBLEWRAP_H
+
+#include "ARIAGridAccessible.h"
+#include "ia2AccessibleTable.h"
+#include "ia2AccessibleTableCell.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * IA2 wrapper class for ARIAGridAccessible implementing IAccessibleTable and
+ * IAccessibleTable2 interfaces.
+ */
+class ARIAGridAccessibleWrap : public ARIAGridAccessible,
+ public ia2AccessibleTable
+{
+ ~ARIAGridAccessibleWrap() {}
+
+public:
+ ARIAGridAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) :
+ ARIAGridAccessible(aContent, aDoc), ia2AccessibleTable(this) {}
+
+ // IUnknown
+ DECL_IUNKNOWN_INHERITED
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ virtual void Shutdown() override;
+};
+
+/**
+ * IA2 wrapper class for ARIAGridCellAccessible implementing
+ * IAccessibleTableCell interface.
+ */
+class ARIAGridCellAccessibleWrap : public ARIAGridCellAccessible,
+ public ia2AccessibleTableCell
+{
+ ~ARIAGridCellAccessibleWrap() {}
+
+public:
+ ARIAGridCellAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) :
+ ARIAGridCellAccessible(aContent, aDoc), ia2AccessibleTableCell(this) {}
+
+ // IUnknown
+ DECL_IUNKNOWN_INHERITED
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ virtual void Shutdown() override;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/msaa/AccessibleWrap.cpp b/accessible/windows/msaa/AccessibleWrap.cpp
new file mode 100644
index 000000000..6112c370a
--- /dev/null
+++ b/accessible/windows/msaa/AccessibleWrap.cpp
@@ -0,0 +1,1686 @@
+/* -*- 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 "Accessible-inl.h"
+
+#include "Compatibility.h"
+#include "DocAccessible-inl.h"
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/a11y/DocAccessibleChild.h"
+#include "mozilla/a11y/DocAccessibleParent.h"
+#include "EnumVariant.h"
+#include "nsAccUtils.h"
+#include "nsCoreUtils.h"
+#include "nsIAccessibleEvent.h"
+#include "nsWinUtils.h"
+#include "mozilla/a11y/ProxyAccessible.h"
+#include "ProxyWrappers.h"
+#include "ServiceProvider.h"
+#include "Relation.h"
+#include "Role.h"
+#include "RootAccessible.h"
+#include "sdnAccessible.h"
+#include "States.h"
+
+#ifdef A11Y_LOG
+#include "Logging.h"
+#endif
+
+#include "nsIMutableArray.h"
+#include "nsIFrame.h"
+#include "nsIScrollableFrame.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "nsIServiceManager.h"
+#include "nsNameSpaceManager.h"
+#include "nsTextFormatter.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "nsEventMap.h"
+#include "nsArrayUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsIXULRuntime.h"
+
+#include "oleacc.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+const uint32_t USE_ROLE_STRING = 0;
+
+/* For documentation of the accessibility architecture,
+ * see http://lxr.mozilla.org/seamonkey/source/accessible/accessible-docs.html
+ */
+
+//#define DEBUG_LEAKS
+
+#ifdef DEBUG_LEAKS
+static gAccessibles = 0;
+#endif
+
+MsaaIdGenerator AccessibleWrap::sIDGen;
+
+static const VARIANT kVarChildIdSelf = {VT_I4};
+
+static const int32_t kIEnumVariantDisconnected = -1;
+
+////////////////////////////////////////////////////////////////////////////////
+// AccessibleWrap
+////////////////////////////////////////////////////////////////////////////////
+AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) :
+ Accessible(aContent, aDoc)
+ , mID(kNoID)
+{
+}
+
+AccessibleWrap::~AccessibleWrap()
+{
+ if (mID != kNoID) {
+ sIDGen.ReleaseID(this);
+ }
+}
+
+ITypeInfo* AccessibleWrap::gTypeInfo = nullptr;
+
+NS_IMPL_ISUPPORTS_INHERITED0(AccessibleWrap, Accessible)
+
+void
+AccessibleWrap::Shutdown()
+{
+ if (mID != kNoID) {
+ auto doc = static_cast<DocAccessibleWrap*>(mDoc.get());
+ MOZ_ASSERT(doc);
+ if (doc) {
+ doc->RemoveID(mID);
+ mID = kNoID;
+ }
+ }
+
+ Accessible::Shutdown();
+}
+
+//-----------------------------------------------------
+// IUnknown interface methods - see iunknown.h for documentation
+//-----------------------------------------------------
+
+// Microsoft COM QueryInterface
+STDMETHODIMP
+AccessibleWrap::QueryInterface(REFIID iid, void** ppv)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!ppv)
+ return E_INVALIDARG;
+
+ *ppv = nullptr;
+
+ if (IID_IUnknown == iid)
+ *ppv = static_cast<IAccessible*>(this);
+ else if (IID_IDispatch == iid || IID_IAccessible == iid)
+ *ppv = static_cast<IAccessible*>(this);
+ else if (IID_IEnumVARIANT == iid && !IsProxy()) {
+ // Don't support this interface for leaf elements.
+ if (!HasChildren() || nsAccUtils::MustPrune(this))
+ return E_NOINTERFACE;
+
+ *ppv = static_cast<IEnumVARIANT*>(new ChildrenEnumVariant(this));
+ } else if (IID_IServiceProvider == iid)
+ *ppv = new ServiceProvider(this);
+ else if (IID_ISimpleDOMNode == iid && !IsProxy()) {
+ if (IsDefunct() || (!HasOwnContent() && !IsDoc()))
+ return E_NOINTERFACE;
+
+ *ppv = static_cast<ISimpleDOMNode*>(new sdnAccessible(GetNode()));
+ }
+
+ if (nullptr == *ppv) {
+ HRESULT hr = ia2Accessible::QueryInterface(iid, ppv);
+ if (SUCCEEDED(hr))
+ return hr;
+ }
+
+ if (nullptr == *ppv && !IsProxy()) {
+ HRESULT hr = ia2AccessibleComponent::QueryInterface(iid, ppv);
+ if (SUCCEEDED(hr))
+ return hr;
+ }
+
+ if (nullptr == *ppv) {
+ HRESULT hr = ia2AccessibleHyperlink::QueryInterface(iid, ppv);
+ if (SUCCEEDED(hr))
+ return hr;
+ }
+
+ if (nullptr == *ppv && !IsProxy()) {
+ HRESULT hr = ia2AccessibleValue::QueryInterface(iid, ppv);
+ if (SUCCEEDED(hr))
+ return hr;
+ }
+
+ if (nullptr == *ppv)
+ return E_NOINTERFACE;
+
+ (reinterpret_cast<IUnknown*>(*ppv))->AddRef();
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+//-----------------------------------------------------
+// IAccessible methods
+//-----------------------------------------------------
+
+STDMETHODIMP
+AccessibleWrap::get_accParent( IDispatch __RPC_FAR *__RPC_FAR *ppdispParent)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!ppdispParent)
+ return E_INVALIDARG;
+
+ *ppdispParent = nullptr;
+
+ if (IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ DocAccessible* doc = AsDoc();
+ if (doc) {
+ // Return window system accessible object for root document and tab document
+ // accessibles.
+ if (!doc->ParentDocument() ||
+ (nsWinUtils::IsWindowEmulationStarted() &&
+ nsCoreUtils::IsTabDocument(doc->DocumentNode()))) {
+ HWND hwnd = static_cast<HWND>(doc->GetNativeWindow());
+ if (hwnd && SUCCEEDED(::AccessibleObjectFromWindow(hwnd, OBJID_WINDOW,
+ IID_IAccessible,
+ (void**)ppdispParent))) {
+ return S_OK;
+ }
+ }
+ }
+
+ Accessible* xpParentAcc = Parent();
+ if (!xpParentAcc)
+ return S_FALSE;
+
+ *ppdispParent = NativeAccessible(xpParentAcc);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+AccessibleWrap::get_accChildCount( long __RPC_FAR *pcountChildren)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!pcountChildren)
+ return E_INVALIDARG;
+
+ *pcountChildren = 0;
+
+ if (IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ if (nsAccUtils::MustPrune(this))
+ return S_OK;
+
+ *pcountChildren = ChildCount();
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+AccessibleWrap::get_accChild(
+ /* [in] */ VARIANT varChild,
+ /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppdispChild)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!ppdispChild)
+ return E_INVALIDARG;
+
+ *ppdispChild = nullptr;
+ if (IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ // IAccessible::accChild is used to return this accessible or child accessible
+ // at the given index or to get an accessible by child ID in the case of
+ // document accessible.
+ // The getting an accessible by child ID is used by AccessibleObjectFromEvent()
+ // called by AT when AT handles our MSAA event.
+ bool isDefunct = false;
+ RefPtr<IAccessible> child = GetIAccessibleFor(varChild, &isDefunct);
+ if (!child) {
+ return E_INVALIDARG;
+ }
+
+ if (isDefunct) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ child.forget(ppdispChild);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+/**
+ * This function is a helper for implementing IAccessible methods that accept
+ * a Child ID as a parameter. If the child ID is CHILDID_SELF, the function
+ * returns S_OK but a null *aOutInterface. Otherwise, *aOutInterface points
+ * to the resolved IAccessible.
+ *
+ * The CHILDID_SELF case is special because in that case we actually execute
+ * the implementation of the IAccessible method, whereas in the non-self case,
+ * we delegate the method call to that object for execution.
+ *
+ * A sample invocation of this would look like:
+ *
+ * RefPtr<IAccessible> accessible;
+ * HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
+ * if (FAILED(hr)) {
+ * return hr;
+ * }
+ *
+ * if (accessible) {
+ * return accessible->get_accFoo(kVarChildIdSelf, pszName);
+ * }
+ *
+ * // Implementation for CHILDID_SELF case goes here
+ */
+HRESULT
+AccessibleWrap::ResolveChild(const VARIANT& aVarChild,
+ IAccessible** aOutInterface)
+{
+ MOZ_ASSERT(aOutInterface);
+ *aOutInterface = nullptr;
+
+ if (aVarChild.vt != VT_I4) {
+ return E_INVALIDARG;
+ }
+
+ if (IsDefunct()) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ if (aVarChild.lVal == CHILDID_SELF) {
+ return S_OK;
+ }
+
+ bool isDefunct = false;
+ RefPtr<IAccessible> accessible = GetIAccessibleFor(aVarChild, &isDefunct);
+ if (!accessible) {
+ return E_INVALIDARG;
+ }
+
+ if (isDefunct) {
+ return CO_E_OBJNOTCONNECTED;
+ }
+
+ accessible.forget(aOutInterface);
+ return S_OK;
+}
+
+STDMETHODIMP
+AccessibleWrap::get_accName(
+ /* [optional][in] */ VARIANT varChild,
+ /* [retval][out] */ BSTR __RPC_FAR *pszName)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!pszName || varChild.vt != VT_I4)
+ return E_INVALIDARG;
+
+ *pszName = nullptr;
+
+ RefPtr<IAccessible> accessible;
+ HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ if (accessible) {
+ return accessible->get_accName(kVarChildIdSelf, pszName);
+ }
+
+ nsAutoString name;
+ Name(name);
+
+ // The name was not provided, e.g. no alt attribute for an image. A screen
+ // reader may choose to invent its own accessible name, e.g. from an image src
+ // attribute. Refer to eNoNameOnPurpose return value.
+ if (name.IsVoid())
+ return S_FALSE;
+
+ *pszName = ::SysAllocStringLen(name.get(), name.Length());
+ if (!*pszName)
+ return E_OUTOFMEMORY;
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+
+STDMETHODIMP
+AccessibleWrap::get_accValue(
+ /* [optional][in] */ VARIANT varChild,
+ /* [retval][out] */ BSTR __RPC_FAR *pszValue)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!pszValue)
+ return E_INVALIDARG;
+
+ *pszValue = nullptr;
+
+ RefPtr<IAccessible> accessible;
+ HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ if (accessible) {
+ return accessible->get_accValue(kVarChildIdSelf, pszValue);
+ }
+
+ nsAutoString value;
+ Value(value);
+
+ // See bug 438784: need to expose URL on doc's value attribute. For this,
+ // reverting part of fix for bug 425693 to make this MSAA method behave
+ // IAccessible2-style.
+ if (value.IsEmpty())
+ return S_FALSE;
+
+ *pszValue = ::SysAllocStringLen(value.get(), value.Length());
+ if (!*pszValue)
+ return E_OUTOFMEMORY;
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+AccessibleWrap::get_accDescription(VARIANT varChild,
+ BSTR __RPC_FAR *pszDescription)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!pszDescription)
+ return E_INVALIDARG;
+
+ *pszDescription = nullptr;
+
+ RefPtr<IAccessible> accessible;
+ HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ if (accessible) {
+ return accessible->get_accDescription(kVarChildIdSelf, pszDescription);
+ }
+
+ nsAutoString description;
+ Description(description);
+
+ *pszDescription = ::SysAllocStringLen(description.get(),
+ description.Length());
+ return *pszDescription ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+AccessibleWrap::get_accRole(
+ /* [optional][in] */ VARIANT varChild,
+ /* [retval][out] */ VARIANT __RPC_FAR *pvarRole)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!pvarRole)
+ return E_INVALIDARG;
+
+ VariantInit(pvarRole);
+
+ RefPtr<IAccessible> accessible;
+ HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ if (accessible) {
+ return accessible->get_accRole(kVarChildIdSelf, pvarRole);
+ }
+
+ a11y::role geckoRole;
+#ifdef DEBUG
+ NS_ASSERTION(nsAccUtils::IsTextInterfaceSupportCorrect(this),
+ "Does not support Text when it should");
+#endif
+
+ geckoRole = Role();
+
+ uint32_t msaaRole = 0;
+
+#define ROLE(_geckoRole, stringRole, atkRole, macRole, \
+ _msaaRole, ia2Role, nameRule) \
+ case roles::_geckoRole: \
+ msaaRole = _msaaRole; \
+ break;
+
+ switch (geckoRole) {
+#include "RoleMap.h"
+ default:
+ MOZ_CRASH("Unknown role.");
+ }
+
+#undef ROLE
+
+ // Special case, if there is a ROLE_ROW inside of a ROLE_TREE_TABLE, then call the MSAA role
+ // a ROLE_OUTLINEITEM for consistency and compatibility.
+ // We need this because ARIA has a role of "row" for both grid and treegrid
+ if (geckoRole == roles::ROW) {
+ Accessible* xpParent = Parent();
+ if (xpParent && xpParent->Role() == roles::TREE_TABLE)
+ msaaRole = ROLE_SYSTEM_OUTLINEITEM;
+ }
+
+ // -- Try enumerated role
+ if (msaaRole != USE_ROLE_STRING) {
+ pvarRole->vt = VT_I4;
+ pvarRole->lVal = msaaRole; // Normal enumerated role
+ return S_OK;
+ }
+
+ // -- Try BSTR role
+ // Could not map to known enumerated MSAA role like ROLE_BUTTON
+ // Use BSTR role to expose role attribute or tag name + namespace
+ nsIContent *content = GetContent();
+ if (!content)
+ return E_FAIL;
+
+ if (content->IsElement()) {
+ nsAutoString roleString;
+ if (msaaRole != ROLE_SYSTEM_CLIENT &&
+ !content->GetAttr(kNameSpaceID_None, nsGkAtoms::role, roleString)) {
+ nsIDocument * document = content->GetUncomposedDoc();
+ if (!document)
+ return E_FAIL;
+
+ dom::NodeInfo *nodeInfo = content->NodeInfo();
+ nodeInfo->GetName(roleString);
+
+ // Only append name space if different from that of current document.
+ if (!nodeInfo->NamespaceEquals(document->GetDefaultNamespaceID())) {
+ nsAutoString nameSpaceURI;
+ nodeInfo->GetNamespaceURI(nameSpaceURI);
+ roleString += NS_LITERAL_STRING(", ") + nameSpaceURI;
+ }
+ }
+
+ if (!roleString.IsEmpty()) {
+ pvarRole->vt = VT_BSTR;
+ pvarRole->bstrVal = ::SysAllocString(roleString.get());
+ return S_OK;
+ }
+ }
+
+ return E_FAIL;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+AccessibleWrap::get_accState(
+ /* [optional][in] */ VARIANT varChild,
+ /* [retval][out] */ VARIANT __RPC_FAR *pvarState)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!pvarState)
+ return E_INVALIDARG;
+
+ VariantInit(pvarState);
+ pvarState->vt = VT_I4;
+ pvarState->lVal = 0;
+
+ RefPtr<IAccessible> accessible;
+ HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ if (accessible) {
+ return accessible->get_accState(kVarChildIdSelf, pvarState);
+ }
+
+ // MSAA only has 31 states and the lowest 31 bits of our state bit mask
+ // are the same states as MSAA.
+ // Note: we map the following Gecko states to different MSAA states:
+ // REQUIRED -> ALERT_LOW
+ // ALERT -> ALERT_MEDIUM
+ // INVALID -> ALERT_HIGH
+ // CHECKABLE -> MARQUEED
+
+ uint64_t state = State();
+
+ uint32_t msaaState = 0;
+ nsAccUtils::To32States(state, &msaaState, nullptr);
+ pvarState->lVal = msaaState;
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+
+STDMETHODIMP
+AccessibleWrap::get_accHelp(
+ /* [optional][in] */ VARIANT varChild,
+ /* [retval][out] */ BSTR __RPC_FAR *pszHelp)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!pszHelp)
+ return E_INVALIDARG;
+
+ *pszHelp = nullptr;
+ return S_FALSE;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+AccessibleWrap::get_accHelpTopic(
+ /* [out] */ BSTR __RPC_FAR *pszHelpFile,
+ /* [optional][in] */ VARIANT varChild,
+ /* [retval][out] */ long __RPC_FAR *pidTopic)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!pszHelpFile || !pidTopic)
+ return E_INVALIDARG;
+
+ *pszHelpFile = nullptr;
+ *pidTopic = 0;
+ return S_FALSE;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+AccessibleWrap::get_accKeyboardShortcut(
+ /* [optional][in] */ VARIANT varChild,
+ /* [retval][out] */ BSTR __RPC_FAR *pszKeyboardShortcut)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!pszKeyboardShortcut)
+ return E_INVALIDARG;
+ *pszKeyboardShortcut = nullptr;
+
+ RefPtr<IAccessible> accessible;
+ HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ if (accessible) {
+ return accessible->get_accKeyboardShortcut(kVarChildIdSelf,
+ pszKeyboardShortcut);
+ }
+
+ KeyBinding keyBinding = AccessKey();
+ if (keyBinding.IsEmpty())
+ keyBinding = KeyboardShortcut();
+
+ nsAutoString shortcut;
+ keyBinding.ToString(shortcut);
+
+ *pszKeyboardShortcut = ::SysAllocStringLen(shortcut.get(),
+ shortcut.Length());
+ return *pszKeyboardShortcut ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+AccessibleWrap::get_accFocus(
+ /* [retval][out] */ VARIANT __RPC_FAR *pvarChild)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!pvarChild)
+ return E_INVALIDARG;
+
+ VariantInit(pvarChild);
+
+ // VT_EMPTY: None. This object does not have the keyboard focus itself
+ // and does not contain a child that has the keyboard focus.
+ // VT_I4: lVal is CHILDID_SELF. The object itself has the keyboard focus.
+ // VT_I4: lVal contains the child ID of the child element with the keyboard focus.
+ // VT_DISPATCH: pdispVal member is the address of the IDispatch interface
+ // for the child object with the keyboard focus.
+ if (IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ // Return the current IAccessible child that has focus
+ Accessible* focusedAccessible = FocusedChild();
+
+ if (focusedAccessible == this) {
+ pvarChild->vt = VT_I4;
+ pvarChild->lVal = CHILDID_SELF;
+ }
+ else if (focusedAccessible) {
+ pvarChild->vt = VT_DISPATCH;
+ pvarChild->pdispVal = NativeAccessible(focusedAccessible);
+ }
+ else {
+ pvarChild->vt = VT_EMPTY; // No focus or focus is not a child
+ }
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+/**
+ * This helper class implements IEnumVARIANT for a nsTArray containing
+ * accessible objects.
+ */
+class AccessibleEnumerator final : public IEnumVARIANT
+{
+public:
+ AccessibleEnumerator(const nsTArray<Accessible*>& aArray) :
+ mArray(aArray), mCurIndex(0) { }
+ AccessibleEnumerator(const AccessibleEnumerator& toCopy) :
+ mArray(toCopy.mArray), mCurIndex(toCopy.mCurIndex) { }
+ ~AccessibleEnumerator() { }
+
+ // IUnknown
+ DECL_IUNKNOWN
+
+ // IEnumVARIANT
+ STDMETHODIMP Next(unsigned long celt, VARIANT FAR* rgvar, unsigned long FAR* pceltFetched);
+ STDMETHODIMP Skip(unsigned long celt);
+ STDMETHODIMP Reset()
+ {
+ mCurIndex = 0;
+ return S_OK;
+ }
+ STDMETHODIMP Clone(IEnumVARIANT FAR* FAR* ppenum);
+
+private:
+ nsTArray<Accessible*> mArray;
+ uint32_t mCurIndex;
+};
+
+STDMETHODIMP
+AccessibleEnumerator::QueryInterface(REFIID iid, void ** ppvObject)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (iid == IID_IEnumVARIANT) {
+ *ppvObject = static_cast<IEnumVARIANT*>(this);
+ AddRef();
+ return S_OK;
+ }
+ if (iid == IID_IUnknown) {
+ *ppvObject = static_cast<IUnknown*>(this);
+ AddRef();
+ return S_OK;
+ }
+
+ *ppvObject = nullptr;
+ return E_NOINTERFACE;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+AccessibleEnumerator::Next(unsigned long celt, VARIANT FAR* rgvar, unsigned long FAR* pceltFetched)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ uint32_t length = mArray.Length();
+ HRESULT hr = S_OK;
+
+ // Can't get more elements than there are...
+ if (celt > length - mCurIndex) {
+ hr = S_FALSE;
+ celt = length - mCurIndex;
+ }
+
+ // Copy the elements of the array into rgvar.
+ for (uint32_t i = 0; i < celt; ++i, ++mCurIndex) {
+ rgvar[i].vt = VT_DISPATCH;
+ rgvar[i].pdispVal = AccessibleWrap::NativeAccessible(mArray[mCurIndex]);
+ }
+
+ if (pceltFetched)
+ *pceltFetched = celt;
+
+ return hr;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+AccessibleEnumerator::Clone(IEnumVARIANT FAR* FAR* ppenum)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ *ppenum = new AccessibleEnumerator(*this);
+ if (!*ppenum)
+ return E_OUTOFMEMORY;
+ NS_ADDREF(*ppenum);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+AccessibleEnumerator::Skip(unsigned long celt)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ uint32_t length = mArray.Length();
+ // Check if we can skip the requested number of elements
+ if (celt > length - mCurIndex) {
+ mCurIndex = length;
+ return S_FALSE;
+ }
+ mCurIndex += celt;
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+/**
+ * This method is called when a client wants to know which children of a node
+ * are selected. Note that this method can only find selected children for
+ * accessible object which implement SelectAccessible.
+ *
+ * The VARIANT return value arguement is expected to either contain a single IAccessible
+ * or an IEnumVARIANT of IAccessibles. We return the IEnumVARIANT regardless of the number
+ * of children selected, unless there are none selected in which case we return an empty
+ * VARIANT.
+ *
+ * We get the selected options from the select's accessible object and wrap
+ * those in an AccessibleEnumerator which we then put in the return VARIANT.
+ *
+ * returns a VT_EMPTY VARIANT if:
+ * - there are no selected children for this object
+ * - the object is not the type that can have children selected
+ */
+STDMETHODIMP
+AccessibleWrap::get_accSelection(VARIANT __RPC_FAR *pvarChildren)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!pvarChildren)
+ return E_INVALIDARG;
+
+ VariantInit(pvarChildren);
+ pvarChildren->vt = VT_EMPTY;
+
+ if (IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ if (IsSelect()) {
+ AutoTArray<Accessible*, 10> selectedItems;
+ SelectedItems(&selectedItems);
+
+ // 1) Create and initialize the enumeration
+ RefPtr<AccessibleEnumerator> pEnum = new AccessibleEnumerator(selectedItems);
+ pvarChildren->vt = VT_UNKNOWN; // this must be VT_UNKNOWN for an IEnumVARIANT
+ NS_ADDREF(pvarChildren->punkVal = pEnum);
+ }
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+AccessibleWrap::get_accDefaultAction(
+ /* [optional][in] */ VARIANT varChild,
+ /* [retval][out] */ BSTR __RPC_FAR *pszDefaultAction)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!pszDefaultAction)
+ return E_INVALIDARG;
+
+ *pszDefaultAction = nullptr;
+
+ RefPtr<IAccessible> accessible;
+ HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ if (accessible) {
+ return accessible->get_accDefaultAction(kVarChildIdSelf, pszDefaultAction);
+ }
+
+ nsAutoString defaultAction;
+ ActionNameAt(0, defaultAction);
+
+ *pszDefaultAction = ::SysAllocStringLen(defaultAction.get(),
+ defaultAction.Length());
+ return *pszDefaultAction ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+AccessibleWrap::accSelect(
+ /* [in] */ long flagsSelect,
+ /* [optional][in] */ VARIANT varChild)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ RefPtr<IAccessible> accessible;
+ HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ if (accessible) {
+ return accessible->accSelect(flagsSelect, kVarChildIdSelf);
+ }
+
+ if (flagsSelect & SELFLAG_TAKEFOCUS) {
+ if (XRE_IsContentProcess()) {
+ // In this case we might have been invoked while the IPC MessageChannel is
+ // waiting on a sync reply. We cannot dispatch additional IPC while that
+ // is happening, so we dispatch TakeFocus from the main thread to
+ // guarantee that we are outside any IPC.
+ nsCOMPtr<nsIRunnable> runnable =
+ mozilla::NewRunnableMethod(this, &Accessible::TakeFocus);
+ NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL);
+ return S_OK;
+ }
+ TakeFocus();
+ return S_OK;
+ }
+
+ if (flagsSelect & SELFLAG_TAKESELECTION) {
+ TakeSelection();
+ return S_OK;
+ }
+
+ if (flagsSelect & SELFLAG_ADDSELECTION) {
+ SetSelected(true);
+ return S_OK;
+ }
+
+ if (flagsSelect & SELFLAG_REMOVESELECTION) {
+ SetSelected(false);
+ return S_OK;
+ }
+
+ return E_FAIL;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+AccessibleWrap::accLocation(
+ /* [out] */ long __RPC_FAR *pxLeft,
+ /* [out] */ long __RPC_FAR *pyTop,
+ /* [out] */ long __RPC_FAR *pcxWidth,
+ /* [out] */ long __RPC_FAR *pcyHeight,
+ /* [optional][in] */ VARIANT varChild)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!pxLeft || !pyTop || !pcxWidth || !pcyHeight)
+ return E_INVALIDARG;
+
+ *pxLeft = 0;
+ *pyTop = 0;
+ *pcxWidth = 0;
+ *pcyHeight = 0;
+
+ RefPtr<IAccessible> accessible;
+ HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ if (accessible) {
+ return accessible->accLocation(pxLeft, pyTop, pcxWidth, pcyHeight,
+ kVarChildIdSelf);
+ }
+
+ nsIntRect rect = Bounds();
+
+ *pxLeft = rect.x;
+ *pyTop = rect.y;
+ *pcxWidth = rect.width;
+ *pcyHeight = rect.height;
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+AccessibleWrap::accNavigate(
+ /* [in] */ long navDir,
+ /* [optional][in] */ VARIANT varStart,
+ /* [retval][out] */ VARIANT __RPC_FAR *pvarEndUpAt)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!pvarEndUpAt)
+ return E_INVALIDARG;
+
+ VariantInit(pvarEndUpAt);
+
+ RefPtr<IAccessible> accessible;
+ HRESULT hr = ResolveChild(varStart, getter_AddRefs(accessible));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ if (accessible) {
+ return accessible->accNavigate(navDir, kVarChildIdSelf, pvarEndUpAt);
+ }
+
+ Accessible* navAccessible = nullptr;
+ Maybe<RelationType> xpRelation;
+
+#define RELATIONTYPE(geckoType, stringType, atkType, msaaType, ia2Type) \
+ case msaaType: \
+ xpRelation.emplace(RelationType::geckoType); \
+ break;
+
+ switch(navDir) {
+ case NAVDIR_FIRSTCHILD:
+ if (IsProxy()) {
+ if (!Proxy()->MustPruneChildren()) {
+ navAccessible = WrapperFor(Proxy()->FirstChild());
+ }
+ } else {
+ if (!nsAccUtils::MustPrune(this))
+ navAccessible = FirstChild();
+ }
+ break;
+ case NAVDIR_LASTCHILD:
+ if (IsProxy()) {
+ if (!Proxy()->MustPruneChildren()) {
+ navAccessible = WrapperFor(Proxy()->LastChild());
+ }
+ } else {
+ if (!nsAccUtils::MustPrune(this))
+ navAccessible = LastChild();
+ }
+ break;
+ case NAVDIR_NEXT:
+ navAccessible = IsProxy()
+ ? WrapperFor(Proxy()->NextSibling())
+ : NextSibling();
+ break;
+ case NAVDIR_PREVIOUS:
+ navAccessible = IsProxy()
+ ? WrapperFor(Proxy()->PrevSibling())
+ : PrevSibling();
+ break;
+ case NAVDIR_DOWN:
+ case NAVDIR_LEFT:
+ case NAVDIR_RIGHT:
+ case NAVDIR_UP:
+ return E_NOTIMPL;
+
+ // MSAA relationship extensions to accNavigate
+#include "RelationTypeMap.h"
+
+ default:
+ return E_INVALIDARG;
+ }
+
+#undef RELATIONTYPE
+
+ pvarEndUpAt->vt = VT_EMPTY;
+
+ if (xpRelation) {
+ Relation rel = RelationByType(*xpRelation);
+ navAccessible = rel.Next();
+ }
+
+ if (!navAccessible)
+ return E_FAIL;
+
+ pvarEndUpAt->pdispVal = NativeAccessible(navAccessible);
+ pvarEndUpAt->vt = VT_DISPATCH;
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+AccessibleWrap::accHitTest(
+ /* [in] */ long xLeft,
+ /* [in] */ long yTop,
+ /* [retval][out] */ VARIANT __RPC_FAR *pvarChild)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!pvarChild)
+ return E_INVALIDARG;
+
+ VariantInit(pvarChild);
+
+ if (IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ Accessible* accessible = ChildAtPoint(xLeft, yTop, eDirectChild);
+
+ // if we got a child
+ if (accessible) {
+ // if the child is us
+ if (accessible == this) {
+ pvarChild->vt = VT_I4;
+ pvarChild->lVal = CHILDID_SELF;
+ } else { // its not create an Accessible for it.
+ pvarChild->vt = VT_DISPATCH;
+ pvarChild->pdispVal = NativeAccessible(accessible);
+ }
+ } else {
+ // no child at that point
+ pvarChild->vt = VT_EMPTY;
+ return S_FALSE;
+ }
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+AccessibleWrap::accDoDefaultAction(
+ /* [optional][in] */ VARIANT varChild)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ RefPtr<IAccessible> accessible;
+ HRESULT hr = ResolveChild(varChild, getter_AddRefs(accessible));
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ if (accessible) {
+ return accessible->accDoDefaultAction(kVarChildIdSelf);
+ }
+
+ return DoAction(0) ? S_OK : E_INVALIDARG;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+AccessibleWrap::put_accName(
+ /* [optional][in] */ VARIANT varChild,
+ /* [in] */ BSTR szName)
+{
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP
+AccessibleWrap::put_accValue(
+ /* [optional][in] */ VARIANT varChild,
+ /* [in] */ BSTR szValue)
+{
+ return E_NOTIMPL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// IDispatch
+
+STDMETHODIMP
+AccessibleWrap::GetTypeInfoCount(UINT *pctinfo)
+{
+ if (!pctinfo)
+ return E_INVALIDARG;
+
+ *pctinfo = 1;
+ return S_OK;
+}
+
+STDMETHODIMP
+AccessibleWrap::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)
+{
+ if (!ppTInfo)
+ return E_INVALIDARG;
+
+ *ppTInfo = nullptr;
+
+ if (iTInfo != 0)
+ return DISP_E_BADINDEX;
+
+ ITypeInfo * typeInfo = GetTI(lcid);
+ if (!typeInfo)
+ return E_FAIL;
+
+ typeInfo->AddRef();
+ *ppTInfo = typeInfo;
+
+ return S_OK;
+}
+
+STDMETHODIMP
+AccessibleWrap::GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames,
+ UINT cNames, LCID lcid, DISPID *rgDispId)
+{
+ ITypeInfo *typeInfo = GetTI(lcid);
+ if (!typeInfo)
+ return E_FAIL;
+
+ HRESULT hr = DispGetIDsOfNames(typeInfo, rgszNames, cNames, rgDispId);
+ return hr;
+}
+
+STDMETHODIMP
+AccessibleWrap::Invoke(DISPID dispIdMember, REFIID riid,
+ LCID lcid, WORD wFlags, DISPPARAMS *pDispParams,
+ VARIANT *pVarResult, EXCEPINFO *pExcepInfo,
+ UINT *puArgErr)
+{
+ ITypeInfo *typeInfo = GetTI(lcid);
+ if (!typeInfo)
+ return E_FAIL;
+
+ return typeInfo->Invoke(static_cast<IAccessible*>(this), dispIdMember,
+ wFlags, pDispParams, pVarResult, pExcepInfo,
+ puArgErr);
+}
+
+void
+AccessibleWrap::GetNativeInterface(void** aOutAccessible)
+{
+ *aOutAccessible = static_cast<IAccessible*>(this);
+ NS_ADDREF_THIS();
+}
+
+void
+AccessibleWrap::SetID(uint32_t aID)
+{
+ MOZ_ASSERT(XRE_IsParentProcess() && IsProxy());
+ mID = aID;
+}
+
+void
+AccessibleWrap::FireWinEvent(Accessible* aTarget, uint32_t aEventType)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ static_assert(sizeof(gWinEventMap)/sizeof(gWinEventMap[0]) == nsIAccessibleEvent::EVENT_LAST_ENTRY,
+ "MSAA event map skewed");
+
+ NS_ASSERTION(aEventType > 0 && aEventType < ArrayLength(gWinEventMap), "invalid event type");
+
+ uint32_t winEvent = gWinEventMap[aEventType];
+ if (!winEvent)
+ return;
+
+ int32_t childID = GetChildIDFor(aTarget);
+ if (!childID)
+ return; // Can't fire an event without a child ID
+
+ HWND hwnd = GetHWNDFor(aTarget);
+ if (!hwnd) {
+ return;
+ }
+
+ // Fire MSAA event for client area window.
+ ::NotifyWinEvent(winEvent, hwnd, OBJID_CLIENT, childID);
+
+ // JAWS announces collapsed combobox navigation based on focus events.
+ if (aEventType == nsIAccessibleEvent::EVENT_SELECTION &&
+ Compatibility::IsJAWS()) {
+ roles::Role role = aTarget->IsProxy() ? aTarget->Proxy()->Role() :
+ aTarget->Role();
+ if (role == roles::COMBOBOX_OPTION) {
+ ::NotifyWinEvent(EVENT_OBJECT_FOCUS, hwnd, OBJID_CLIENT, childID);
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible
+
+nsresult
+AccessibleWrap::HandleAccEvent(AccEvent* aEvent)
+{
+ nsresult rv = Accessible::HandleAccEvent(aEvent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (IPCAccessibilityActive()) {
+ return NS_OK;
+ }
+
+ uint32_t eventType = aEvent->GetEventType();
+
+ // Means we're not active.
+ NS_ENSURE_TRUE(!IsDefunct(), NS_ERROR_FAILURE);
+
+ Accessible* accessible = aEvent->GetAccessible();
+ if (!accessible)
+ return NS_OK;
+
+ if (eventType == nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED ||
+ eventType == nsIAccessibleEvent::EVENT_FOCUS) {
+ UpdateSystemCaretFor(accessible);
+ }
+
+ FireWinEvent(accessible, eventType);
+
+ return NS_OK;
+}
+
+DocProxyAccessibleWrap*
+AccessibleWrap::DocProxyWrapper() const
+{
+ MOZ_ASSERT(IsProxy());
+
+ ProxyAccessible* proxy = Proxy();
+ if (!proxy) {
+ return nullptr;
+ }
+
+ AccessibleWrap* acc = WrapperFor(proxy->Document());
+ MOZ_ASSERT(acc->IsDoc());
+
+ return static_cast<DocProxyAccessibleWrap*>(acc);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AccessibleWrap
+
+//------- Helper methods ---------
+
+int32_t
+AccessibleWrap::GetChildIDFor(Accessible* aAccessible)
+{
+ // A child ID of the window is required, when we use NotifyWinEvent,
+ // so that the 3rd party application can call back and get the IAccessible
+ // the event occurred on.
+
+ if (!aAccessible) {
+ return 0;
+ }
+
+ // Chrome should use mID which has been generated by the content process.
+ if (aAccessible->IsProxy()) {
+ const uint32_t id = static_cast<AccessibleWrap*>(aAccessible)->mID;
+ MOZ_ASSERT(id != kNoID);
+ return id;
+ }
+
+ if (!aAccessible->Document())
+ return 0;
+
+ uint32_t* id = & static_cast<AccessibleWrap*>(aAccessible)->mID;
+ if (*id != kNoID)
+ return *id;
+
+ *id = sIDGen.GetID();
+
+ MOZ_ASSERT(!aAccessible->IsProxy());
+ DocAccessibleWrap* doc =
+ static_cast<DocAccessibleWrap*>(aAccessible->Document());
+ doc->AddID(*id, static_cast<AccessibleWrap*>(aAccessible));
+
+ return *id;
+}
+
+HWND
+AccessibleWrap::GetHWNDFor(Accessible* aAccessible)
+{
+ if (!aAccessible) {
+ return nullptr;
+ }
+
+ if (XRE_IsContentProcess()) {
+ DocAccessible* doc = aAccessible->Document();
+ if (!doc) {
+ return nullptr;
+ }
+
+ DocAccessibleChild* ipcDoc = doc->IPCDoc();
+ if (!ipcDoc) {
+ return nullptr;
+ }
+
+ auto tab = static_cast<dom::TabChild*>(ipcDoc->Manager());
+ MOZ_ASSERT(tab);
+ return reinterpret_cast<HWND>(tab->GetNativeWindowHandle());
+ }
+
+ // Accessibles in child processes are said to have the HWND of the window
+ // their tab is within. Popups are always in the parent process, and so
+ // never proxied, which means this is basically correct.
+ if (aAccessible->IsProxy()) {
+ ProxyAccessible* proxy = aAccessible->Proxy();
+ if (!proxy) {
+ return nullptr;
+ }
+
+ Accessible* outerDoc = proxy->OuterDocOfRemoteBrowser();
+ NS_ASSERTION(outerDoc, "no outer doc for accessible remote tab!");
+ if (!outerDoc) {
+ return nullptr;
+ }
+
+ return GetHWNDFor(outerDoc);
+ }
+
+ DocAccessible* document = aAccessible->Document();
+ if(!document)
+ return nullptr;
+
+ // Popup lives in own windows, use its HWND until the popup window is
+ // hidden to make old JAWS versions work with collapsed comboboxes (see
+ // discussion in bug 379678).
+ nsIFrame* frame = aAccessible->GetFrame();
+ if (frame) {
+ nsIWidget* widget = frame->GetNearestWidget();
+ if (widget && widget->IsVisible()) {
+ nsIPresShell* shell = document->PresShell();
+ nsViewManager* vm = shell->GetViewManager();
+ if (vm) {
+ nsCOMPtr<nsIWidget> rootWidget;
+ vm->GetRootWidget(getter_AddRefs(rootWidget));
+ // Make sure the accessible belongs to popup. If not then use
+ // document HWND (which might be different from root widget in the
+ // case of window emulation).
+ if (rootWidget != widget)
+ return static_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW));
+ }
+ }
+ }
+
+ return static_cast<HWND>(document->GetNativeWindow());
+}
+
+IDispatch*
+AccessibleWrap::NativeAccessible(Accessible* aAccessible)
+{
+ if (!aAccessible) {
+ NS_WARNING("Not passing in an aAccessible");
+ return nullptr;
+ }
+
+ IAccessible* msaaAccessible = nullptr;
+ aAccessible->GetNativeInterface(reinterpret_cast<void**>(&msaaAccessible));
+ return static_cast<IDispatch*>(msaaAccessible);
+}
+
+static Accessible*
+GetAccessibleInSubtree(DocAccessible* aDoc, uint32_t aID)
+{
+ Accessible* child = static_cast<DocAccessibleWrap*>(aDoc)->GetAccessibleByID(aID);
+ if (child)
+ return child;
+
+ uint32_t childDocCount = aDoc->ChildDocumentCount();
+ for (uint32_t i = 0; i < childDocCount; i++) {
+ child = GetAccessibleInSubtree(aDoc->GetChildDocumentAt(i), aID);
+ if (child)
+ return child;
+ }
+
+ return nullptr;
+ }
+
+static already_AddRefed<IDispatch>
+GetProxiedAccessibleInSubtree(const DocAccessibleParent* aDoc,
+ const VARIANT& aVarChild)
+{
+ auto wrapper = static_cast<DocProxyAccessibleWrap*>(WrapperFor(aDoc));
+ RefPtr<IAccessible> comProxy;
+ int32_t wrapperChildId = AccessibleWrap::GetChildIDFor(wrapper);
+ if (wrapperChildId == aVarChild.lVal) {
+ wrapper->GetNativeInterface(getter_AddRefs(comProxy));
+ return comProxy.forget();
+ }
+
+ MOZ_ASSERT(aDoc->IsTopLevel());
+ if (!aDoc->IsTopLevel()) {
+ return nullptr;
+ }
+
+ wrapper->GetNativeInterface(getter_AddRefs(comProxy));
+ MOZ_ASSERT(comProxy);
+ if (!comProxy) {
+ return nullptr;
+ }
+
+ RefPtr<IDispatch> disp;
+ if (FAILED(comProxy->get_accChild(aVarChild, getter_AddRefs(disp)))) {
+ return nullptr;
+ }
+
+ return disp.forget();
+}
+
+already_AddRefed<IAccessible>
+AccessibleWrap::GetIAccessibleFor(const VARIANT& aVarChild, bool* aIsDefunct)
+{
+ if (aVarChild.vt != VT_I4)
+ return nullptr;
+
+ VARIANT varChild = aVarChild;
+
+ MOZ_ASSERT(aIsDefunct);
+ *aIsDefunct = false;
+
+ RefPtr<IAccessible> result;
+
+ if (varChild.lVal == CHILDID_SELF) {
+ *aIsDefunct = IsDefunct();
+ if (*aIsDefunct) {
+ return nullptr;
+ }
+ GetNativeInterface(getter_AddRefs(result));
+ if (result) {
+ return result.forget();
+ }
+ // If we're not a proxy, there's nothing more we can do to attempt to
+ // resolve the IAccessible, so we just fail.
+ if (!IsProxy()) {
+ return nullptr;
+ }
+ // Otherwise, since we're a proxy and we have a null native interface, this
+ // indicates that we need to obtain a COM proxy. To do this, we'll replace
+ // CHILDID_SELF with our real MSAA ID and continue the search from there.
+ varChild.lVal = GetExistingID();
+ }
+
+ if (IsProxy() ? Proxy()->MustPruneChildren() : nsAccUtils::MustPrune(this)) {
+ return nullptr;
+ }
+
+ // If the MSAA ID is not a chrome id then we already know that we won't
+ // find it here and should look remotely instead.
+ if (XRE_IsParentProcess() && !sIDGen.IsChromeID(varChild.lVal)) {
+ return GetRemoteIAccessibleFor(varChild);
+ }
+ MOZ_ASSERT(XRE_IsParentProcess() ||
+ sIDGen.IsIDForThisContentProcess(varChild.lVal));
+
+ if (varChild.lVal > 0) {
+ // Gecko child indices are 0-based in contrast to indices used in MSAA.
+ MOZ_ASSERT(!IsProxy());
+ Accessible* xpAcc = GetChildAt(varChild.lVal - 1);
+ if (!xpAcc) {
+ return nullptr;
+ }
+ *aIsDefunct = xpAcc->IsDefunct();
+ static_cast<AccessibleWrap*>(xpAcc)->GetNativeInterface(getter_AddRefs(result));
+ return result.forget();
+ }
+
+ // If lVal negative then it is treated as child ID and we should look for
+ // accessible through whole accessible subtree including subdocuments.
+ // Otherwise we treat lVal as index in parent.
+ // First handle the case that both this accessible and the id'd one are in
+ // this process.
+ if (!IsProxy()) {
+ DocAccessible* document = Document();
+ Accessible* child =
+ GetAccessibleInSubtree(document, static_cast<uint32_t>(varChild.lVal));
+
+ // If it is a document then just return an accessible.
+ if (child && IsDoc()) {
+ *aIsDefunct = child->IsDefunct();
+ static_cast<AccessibleWrap*>(child)->GetNativeInterface(getter_AddRefs(result));
+ return result.forget();
+ }
+
+ // Otherwise check whether the accessible is a child (this path works for
+ // ARIA documents and popups).
+ Accessible* parent = child;
+ while (parent && parent != document) {
+ if (parent == this) {
+ *aIsDefunct = child->IsDefunct();
+ static_cast<AccessibleWrap*>(child)->GetNativeInterface(getter_AddRefs(result));
+ return result.forget();
+ }
+
+ parent = parent->Parent();
+ }
+ }
+
+ // Now see about the case that both this accessible and the target one are
+ // proxied.
+ if (IsProxy()) {
+ DocAccessibleParent* proxyDoc = Proxy()->Document();
+ RefPtr<IDispatch> disp = GetProxiedAccessibleInSubtree(proxyDoc, varChild);
+ if (!disp) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(mscom::IsProxy(disp));
+ DebugOnly<HRESULT> hr = disp->QueryInterface(IID_IAccessible,
+ getter_AddRefs(result));
+ MOZ_ASSERT(SUCCEEDED(hr));
+ return result.forget();
+ }
+
+ return nullptr;
+}
+
+already_AddRefed<IAccessible>
+AccessibleWrap::GetRemoteIAccessibleFor(const VARIANT& aVarChild)
+{
+ DocAccessibleParent* proxyDoc = nullptr;
+ DocAccessible* doc = Document();
+ const nsTArray<DocAccessibleParent*>* remoteDocs =
+ DocManager::TopLevelRemoteDocs();
+ if (!remoteDocs) {
+ return nullptr;
+ }
+
+ RefPtr<IAccessible> result;
+
+ size_t docCount = remoteDocs->Length();
+ for (size_t i = 0; i < docCount; i++) {
+ DocAccessibleParent* remoteDoc = remoteDocs->ElementAt(i);
+
+ uint32_t remoteDocMsaaId = WrapperFor(remoteDoc)->GetExistingID();
+ if (!sIDGen.IsSameContentProcessFor(aVarChild.lVal, remoteDocMsaaId)) {
+ continue;
+ }
+
+ Accessible* outerDoc = remoteDoc->OuterDocOfRemoteBrowser();
+ if (!outerDoc) {
+ continue;
+ }
+
+ if (outerDoc->Document() != doc) {
+ continue;
+ }
+
+ RefPtr<IDispatch> disp =
+ GetProxiedAccessibleInSubtree(remoteDoc, aVarChild);
+ if (!disp) {
+ continue;
+ }
+
+ DebugOnly<HRESULT> hr = disp->QueryInterface(IID_IAccessible,
+ getter_AddRefs(result));
+ MOZ_ASSERT(SUCCEEDED(hr));
+ return result.forget();
+ }
+
+ return nullptr;
+}
+
+void
+AccessibleWrap::UpdateSystemCaretFor(Accessible* aAccessible)
+{
+ // Move the system caret so that Windows Tablet Edition and tradional ATs with
+ // off-screen model can follow the caret
+ ::DestroyCaret();
+
+ HyperTextAccessible* text = aAccessible->AsHyperText();
+ if (!text)
+ return;
+
+ nsIWidget* widget = nullptr;
+ LayoutDeviceIntRect caretRect = text->GetCaretRect(&widget);
+ HWND caretWnd;
+ if (caretRect.IsEmpty() || !(caretWnd = (HWND)widget->GetNativeData(NS_NATIVE_WINDOW))) {
+ return;
+ }
+
+ // Create invisible bitmap for caret, otherwise its appearance interferes
+ // with Gecko caret
+ HBITMAP caretBitMap = CreateBitmap(1, caretRect.height, 1, 1, nullptr);
+ if (::CreateCaret(caretWnd, caretBitMap, 1, caretRect.height)) { // Also destroys the last caret
+ ::ShowCaret(caretWnd);
+ RECT windowRect;
+ ::GetWindowRect(caretWnd, &windowRect);
+ ::SetCaretPos(caretRect.x - windowRect.left, caretRect.y - windowRect.top);
+ ::DeleteObject(caretBitMap);
+ }
+}
+
+ITypeInfo*
+AccessibleWrap::GetTI(LCID lcid)
+{
+ if (gTypeInfo)
+ return gTypeInfo;
+
+ ITypeLib *typeLib = nullptr;
+ HRESULT hr = LoadRegTypeLib(LIBID_Accessibility, 1, 0, lcid, &typeLib);
+ if (FAILED(hr))
+ return nullptr;
+
+ hr = typeLib->GetTypeInfoOfGuid(IID_IAccessible, &gTypeInfo);
+ typeLib->Release();
+
+ if (FAILED(hr))
+ return nullptr;
+
+ return gTypeInfo;
+}
+
+/* static */
+uint32_t
+AccessibleWrap::GetContentProcessIdFor(dom::ContentParentId aIPCContentId)
+{
+ return sIDGen.GetContentProcessIDFor(aIPCContentId);
+}
+
+/* static */
+void
+AccessibleWrap::ReleaseContentProcessIdFor(dom::ContentParentId aIPCContentId)
+{
+ sIDGen.ReleaseContentProcessIDFor(aIPCContentId);
+}
diff --git a/accessible/windows/msaa/AccessibleWrap.h b/accessible/windows/msaa/AccessibleWrap.h
new file mode 100644
index 000000000..eb97c2667
--- /dev/null
+++ b/accessible/windows/msaa/AccessibleWrap.h
@@ -0,0 +1,265 @@
+/* -*- 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_AccessibleWrap_h_
+#define mozilla_a11y_AccessibleWrap_h_
+
+#include "nsCOMPtr.h"
+#include "Accessible.h"
+#include "Accessible2.h"
+#include "ia2Accessible.h"
+#include "ia2AccessibleComponent.h"
+#include "ia2AccessibleHyperlink.h"
+#include "ia2AccessibleValue.h"
+#include "mozilla/a11y/MsaaIdGenerator.h"
+#include "mozilla/a11y/ProxyAccessible.h"
+#include "mozilla/Attributes.h"
+
+#ifdef __GNUC__
+// Inheriting from both XPCOM and MSCOM interfaces causes a lot of warnings
+// about virtual functions being hidden by each other. This is done by
+// design, so silence the warning.
+#pragma GCC diagnostic ignored "-Woverloaded-virtual"
+#endif
+
+namespace mozilla {
+namespace a11y {
+class DocProxyAccessibleWrap;
+
+class AccessibleWrap : public Accessible,
+ public ia2Accessible,
+ public ia2AccessibleComponent,
+ public ia2AccessibleHyperlink,
+ public ia2AccessibleValue
+{
+public: // construction, destruction
+ AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ public: // IUnknown methods - see iunknown.h for documentation
+ STDMETHODIMP QueryInterface(REFIID, void**) override;
+
+ // Return the registered OLE class ID of this object's CfDataObj.
+ CLSID GetClassID() const;
+
+ public: // COM interface IAccessible
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accParent(
+ /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppdispParent) override;
+
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accChildCount(
+ /* [retval][out] */ long __RPC_FAR *pcountChildren) override;
+
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accChild(
+ /* [in] */ VARIANT varChild,
+ /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppdispChild) override;
+
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accName(
+ /* [optional][in] */ VARIANT varChild,
+ /* [retval][out] */ BSTR __RPC_FAR *pszName) override;
+
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accValue(
+ /* [optional][in] */ VARIANT varChild,
+ /* [retval][out] */ BSTR __RPC_FAR *pszValue) override;
+
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accDescription(
+ /* [optional][in] */ VARIANT varChild,
+ /* [retval][out] */ BSTR __RPC_FAR *pszDescription) override;
+
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accRole(
+ /* [optional][in] */ VARIANT varChild,
+ /* [retval][out] */ VARIANT __RPC_FAR *pvarRole) override;
+
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accState(
+ /* [optional][in] */ VARIANT varChild,
+ /* [retval][out] */ VARIANT __RPC_FAR *pvarState) override;
+
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accHelp(
+ /* [optional][in] */ VARIANT varChild,
+ /* [retval][out] */ BSTR __RPC_FAR *pszHelp) override;
+
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accHelpTopic(
+ /* [out] */ BSTR __RPC_FAR *pszHelpFile,
+ /* [optional][in] */ VARIANT varChild,
+ /* [retval][out] */ long __RPC_FAR *pidTopic) override;
+
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accKeyboardShortcut(
+ /* [optional][in] */ VARIANT varChild,
+ /* [retval][out] */ BSTR __RPC_FAR *pszKeyboardShortcut) override;
+
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accFocus(
+ /* [retval][out] */ VARIANT __RPC_FAR *pvarChild) override;
+
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accSelection(
+ /* [retval][out] */ VARIANT __RPC_FAR *pvarChildren) override;
+
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accDefaultAction(
+ /* [optional][in] */ VARIANT varChild,
+ /* [retval][out] */ BSTR __RPC_FAR *pszDefaultAction) override;
+
+ virtual /* [id] */ HRESULT STDMETHODCALLTYPE accSelect(
+ /* [in] */ long flagsSelect,
+ /* [optional][in] */ VARIANT varChild) override;
+
+ virtual /* [id] */ HRESULT STDMETHODCALLTYPE accLocation(
+ /* [out] */ long __RPC_FAR *pxLeft,
+ /* [out] */ long __RPC_FAR *pyTop,
+ /* [out] */ long __RPC_FAR *pcxWidth,
+ /* [out] */ long __RPC_FAR *pcyHeight,
+ /* [optional][in] */ VARIANT varChild) override;
+
+ virtual /* [id] */ HRESULT STDMETHODCALLTYPE accNavigate(
+ /* [in] */ long navDir,
+ /* [optional][in] */ VARIANT varStart,
+ /* [retval][out] */ VARIANT __RPC_FAR *pvarEndUpAt) override;
+
+ virtual /* [id] */ HRESULT STDMETHODCALLTYPE accHitTest(
+ /* [in] */ long xLeft,
+ /* [in] */ long yTop,
+ /* [retval][out] */ VARIANT __RPC_FAR *pvarChild) override;
+
+ virtual /* [id] */ HRESULT STDMETHODCALLTYPE accDoDefaultAction(
+ /* [optional][in] */ VARIANT varChild) override;
+
+ virtual /* [id][propput] */ HRESULT STDMETHODCALLTYPE put_accName(
+ /* [optional][in] */ VARIANT varChild,
+ /* [in] */ BSTR szName) override;
+
+ virtual /* [id][propput] */ HRESULT STDMETHODCALLTYPE put_accValue(
+ /* [optional][in] */ VARIANT varChild,
+ /* [in] */ BSTR szValue) override;
+
+ // IDispatch (support of scripting languages like VB)
+ virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(UINT *pctinfo) override;
+
+ virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(UINT iTInfo, LCID lcid,
+ ITypeInfo **ppTInfo) override;
+
+ virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID riid,
+ LPOLESTR *rgszNames,
+ UINT cNames,
+ LCID lcid,
+ DISPID *rgDispId) override;
+
+ virtual HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID riid,
+ LCID lcid, WORD wFlags,
+ DISPPARAMS *pDispParams,
+ VARIANT *pVarResult,
+ EXCEPINFO *pExcepInfo,
+ UINT *puArgErr) override;
+
+ // Accessible
+ virtual nsresult HandleAccEvent(AccEvent* aEvent) override;
+ virtual void Shutdown() override;
+
+ // Helper methods
+ static int32_t GetChildIDFor(Accessible* aAccessible);
+ static HWND GetHWNDFor(Accessible* aAccessible);
+
+ static void FireWinEvent(Accessible* aTarget, uint32_t aEventType);
+
+ /**
+ * System caret support: update the Windows caret position.
+ * The system caret works more universally than the MSAA caret
+ * For example, Window-Eyes, JAWS, ZoomText and Windows Tablet Edition use it
+ * We will use an invisible system caret.
+ * Gecko is still responsible for drawing its own caret
+ */
+ void UpdateSystemCaretFor(Accessible* aAccessible);
+
+ /**
+ * Find an accessible by the given child ID in cached documents.
+ */
+ MOZ_MUST_USE already_AddRefed<IAccessible>
+ GetIAccessibleFor(const VARIANT& aVarChild, bool* aIsDefunct);
+
+ virtual void GetNativeInterface(void **aOutAccessible) override;
+
+ static IDispatch* NativeAccessible(Accessible* aAccessible);
+
+ uint32_t GetExistingID() const { return mID; }
+ static const uint32_t kNoID = 0;
+ void SetID(uint32_t aID);
+
+ static uint32_t GetContentProcessIdFor(dom::ContentParentId aIPCContentId);
+ static void ReleaseContentProcessIdFor(dom::ContentParentId aIPCContentId);
+
+protected:
+ virtual ~AccessibleWrap();
+
+ uint32_t mID;
+
+ HRESULT
+ ResolveChild(const VARIANT& aVarChild, IAccessible** aOutInterface);
+
+ /**
+ * Find a remote accessible by the given child ID.
+ */
+ MOZ_MUST_USE already_AddRefed<IAccessible>
+ GetRemoteIAccessibleFor(const VARIANT& aVarChild);
+
+ /**
+ * Return the wrapper for the document's proxy.
+ */
+ DocProxyAccessibleWrap* DocProxyWrapper() const;
+
+ /**
+ * Creates ITypeInfo for LIBID_Accessibility if it's needed and returns it.
+ */
+ static ITypeInfo* GetTI(LCID lcid);
+
+ static ITypeInfo* gTypeInfo;
+
+ static MsaaIdGenerator sIDGen;
+
+ enum navRelations {
+ NAVRELATION_CONTROLLED_BY = 0x1000,
+ NAVRELATION_CONTROLLER_FOR = 0x1001,
+ NAVRELATION_LABEL_FOR = 0x1002,
+ NAVRELATION_LABELLED_BY = 0x1003,
+ NAVRELATION_MEMBER_OF = 0x1004,
+ NAVRELATION_NODE_CHILD_OF = 0x1005,
+ NAVRELATION_FLOWS_TO = 0x1006,
+ NAVRELATION_FLOWS_FROM = 0x1007,
+ NAVRELATION_SUBWINDOW_OF = 0x1008,
+ NAVRELATION_EMBEDS = 0x1009,
+ NAVRELATION_EMBEDDED_BY = 0x100a,
+ NAVRELATION_POPUP_FOR = 0x100b,
+ NAVRELATION_PARENT_WINDOW_OF = 0x100c,
+ NAVRELATION_DEFAULT_BUTTON = 0x100d,
+ NAVRELATION_DESCRIBED_BY = 0x100e,
+ NAVRELATION_DESCRIPTION_FOR = 0x100f,
+ NAVRELATION_NODE_PARENT_OF = 0x1010,
+ NAVRELATION_CONTAINING_DOCUMENT = 0x1011,
+ NAVRELATION_CONTAINING_TAB_PANE = 0x1012,
+ NAVRELATION_CONTAINING_APPLICATION = 0x1014,
+ NAVRELATION_DETAILS = 0x1015,
+ NAVRELATION_DETAILS_FOR = 0x1016,
+ NAVRELATION_ERROR = 0x1017,
+ NAVRELATION_ERROR_FOR = 0x1018
+ };
+};
+
+static inline AccessibleWrap*
+WrapperFor(const ProxyAccessible* aProxy)
+{
+ return reinterpret_cast<AccessibleWrap*>(aProxy->GetWrapper());
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#ifdef XP_WIN
+// Undo the windows.h damage
+#undef GetMessage
+#undef CreateEvent
+#undef GetClassName
+#undef GetBinaryType
+#undef RemoveDirectory
+#endif
+
+#endif
diff --git a/accessible/windows/msaa/ApplicationAccessibleWrap.cpp b/accessible/windows/msaa/ApplicationAccessibleWrap.cpp
new file mode 100644
index 000000000..b78a8dd55
--- /dev/null
+++ b/accessible/windows/msaa/ApplicationAccessibleWrap.cpp
@@ -0,0 +1,162 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* 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 "AccessibleApplication_i.c"
+#include "IUnknownImpl.h"
+
+#include "nsIGfxInfo.h"
+#include "nsIPersistentProperties2.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Services.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports
+NS_IMPL_ISUPPORTS_INHERITED0(ApplicationAccessibleWrap,
+ ApplicationAccessible)
+
+already_AddRefed<nsIPersistentProperties>
+ApplicationAccessibleWrap::NativeAttributes()
+{
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID);
+
+ nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
+ if (gfxInfo) {
+ bool isD2DEnabled = false;
+ gfxInfo->GetD2DEnabled(&isD2DEnabled);
+ nsAutoString unused;
+ attributes->SetStringProperty(
+ NS_LITERAL_CSTRING("D2D"),
+ isD2DEnabled ? NS_LITERAL_STRING("true") : NS_LITERAL_STRING("false"),
+ unused);
+ }
+
+ return attributes.forget();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// IUnknown
+
+STDMETHODIMP
+ApplicationAccessibleWrap::QueryInterface(REFIID iid, void** ppv)
+{
+ if (!ppv)
+ return E_INVALIDARG;
+
+ *ppv = nullptr;
+
+ if (IID_IAccessibleApplication == iid) {
+ *ppv = static_cast<IAccessibleApplication*>(this);
+ (reinterpret_cast<IUnknown*>(*ppv))->AddRef();
+ return S_OK;
+ }
+
+ return AccessibleWrap::QueryInterface(iid, ppv);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// IAccessibleApplication
+
+STDMETHODIMP
+ApplicationAccessibleWrap::get_appName(BSTR* aName)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aName)
+ return E_INVALIDARG;
+
+ *aName = nullptr;
+
+ if (IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsAutoString name;
+ AppName(name);
+ if (name.IsEmpty())
+ return S_FALSE;
+
+ *aName = ::SysAllocStringLen(name.get(), name.Length());
+ return *aName ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ApplicationAccessibleWrap::get_appVersion(BSTR* aVersion)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aVersion)
+ return E_INVALIDARG;
+
+ *aVersion = nullptr;
+
+ if (IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsAutoString version;
+ AppVersion(version);
+ if (version.IsEmpty())
+ return S_FALSE;
+
+ *aVersion = ::SysAllocStringLen(version.get(), version.Length());
+ return *aVersion ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ApplicationAccessibleWrap::get_toolkitName(BSTR* aName)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aName)
+ return E_INVALIDARG;
+
+ if (IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsAutoString name;
+ PlatformName(name);
+ if (name.IsEmpty())
+ return S_FALSE;
+
+ *aName = ::SysAllocStringLen(name.get(), name.Length());
+ return *aName ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ApplicationAccessibleWrap::get_toolkitVersion(BSTR* aVersion)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aVersion)
+ return E_INVALIDARG;
+
+ *aVersion = nullptr;
+
+ if (IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsAutoString version;
+ PlatformVersion(version);
+ if (version.IsEmpty())
+ return S_FALSE;
+
+ *aVersion = ::SysAllocStringLen(version.get(), version.Length());
+ return *aVersion ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
diff --git a/accessible/windows/msaa/ApplicationAccessibleWrap.h b/accessible/windows/msaa/ApplicationAccessibleWrap.h
new file mode 100644
index 000000000..a427e98f5
--- /dev/null
+++ b/accessible/windows/msaa/ApplicationAccessibleWrap.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* 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"
+
+#include "AccessibleApplication.h"
+
+namespace mozilla {
+namespace a11y {
+
+class ApplicationAccessibleWrap: public ApplicationAccessible,
+ public IAccessibleApplication
+{
+ ~ApplicationAccessibleWrap() {}
+
+public:
+ // nsISupporst
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsAccessible
+ virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override;
+
+ // IUnknown
+ STDMETHODIMP QueryInterface(REFIID, void**);
+
+ // IAccessibleApplication
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_appName(
+ /* [retval][out] */ BSTR *name);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_appVersion(
+ /* [retval][out] */ BSTR *version);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_toolkitName(
+ /* [retval][out] */ BSTR *name);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_toolkitVersion(
+ /* [retval][out] */ BSTR *version);
+
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/windows/msaa/Compatibility.cpp b/accessible/windows/msaa/Compatibility.cpp
new file mode 100644
index 000000000..31026c586
--- /dev/null
+++ b/accessible/windows/msaa/Compatibility.cpp
@@ -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/. */
+
+#include "Compatibility.h"
+
+#include "nsWinUtils.h"
+#include "Statistics.h"
+
+#include "mozilla/Preferences.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+/**
+ * Return true if module version is lesser than the given version.
+ */
+bool
+IsModuleVersionLessThan(HMODULE aModuleHandle, DWORD aMajor, DWORD aMinor)
+{
+ wchar_t fileName[MAX_PATH];
+ ::GetModuleFileNameW(aModuleHandle, fileName, MAX_PATH);
+
+ DWORD dummy = 0;
+ DWORD length = ::GetFileVersionInfoSizeW(fileName, &dummy);
+
+ LPBYTE versionInfo = new BYTE[length];
+ ::GetFileVersionInfoW(fileName, 0, length, versionInfo);
+
+ UINT uLen;
+ VS_FIXEDFILEINFO* fixedFileInfo = nullptr;
+ ::VerQueryValueW(versionInfo, L"\\", (LPVOID*)&fixedFileInfo, &uLen);
+ DWORD dwFileVersionMS = fixedFileInfo->dwFileVersionMS;
+ DWORD dwFileVersionLS = fixedFileInfo->dwFileVersionLS;
+ delete [] versionInfo;
+
+ DWORD dwLeftMost = HIWORD(dwFileVersionMS);
+ DWORD dwSecondRight = HIWORD(dwFileVersionLS);
+ return (dwLeftMost < aMajor ||
+ (dwLeftMost == aMajor && dwSecondRight < aMinor));
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Compatibility
+////////////////////////////////////////////////////////////////////////////////
+
+uint32_t Compatibility::sConsumers = Compatibility::UNKNOWN;
+
+void
+Compatibility::Init()
+{
+ // Note we collect some AT statistics/telemetry here for convenience.
+
+ HMODULE jawsHandle = ::GetModuleHandleW(L"jhook");
+ if (jawsHandle)
+ sConsumers |= (IsModuleVersionLessThan(jawsHandle, 8, 2173)) ?
+ OLDJAWS : JAWS;
+
+ if (::GetModuleHandleW(L"gwm32inc"))
+ sConsumers |= WE;
+
+ if (::GetModuleHandleW(L"dolwinhk"))
+ sConsumers |= DOLPHIN;
+
+ if (::GetModuleHandleW(L"STSA32"))
+ sConsumers |= SEROTEK;
+
+ if (::GetModuleHandleW(L"nvdaHelperRemote"))
+ sConsumers |= NVDA;
+
+ if (::GetModuleHandleW(L"OsmHooks"))
+ sConsumers |= COBRA;
+
+ if (::GetModuleHandleW(L"WebFinderRemote"))
+ sConsumers |= ZOOMTEXT;
+
+ if (::GetModuleHandleW(L"Kazahook"))
+ sConsumers |= KAZAGURU;
+
+ if (::GetModuleHandleW(L"TextExtractorImpl32") ||
+ ::GetModuleHandleW(L"TextExtractorImpl64"))
+ sConsumers |= YOUDAO;
+
+ if (::GetModuleHandleW(L"uiautomation") ||
+ ::GetModuleHandleW(L"uiautomationcore"))
+ sConsumers |= UIAUTOMATION;
+
+ // If we have a known consumer remove the unknown bit.
+ if (sConsumers != Compatibility::UNKNOWN)
+ sConsumers ^= Compatibility::UNKNOWN;
+
+ // Gather telemetry
+ uint32_t temp = sConsumers;
+ for (int i = 0; temp; i++) {
+ if (temp & 0x1)
+ statistics::A11yConsumers(i);
+
+ temp >>= 1;
+ }
+
+ // Turn off new tab switching for Jaws and WE.
+ if (sConsumers & (JAWS | OLDJAWS | WE)) {
+ // Check to see if the pref for disallowing CtrlTab is already set. If so,
+ // bail out (respect the user settings). If not, set it.
+ if (!Preferences::HasUserValue("browser.ctrlTab.disallowForScreenReaders"))
+ Preferences::SetBool("browser.ctrlTab.disallowForScreenReaders", true);
+ }
+}
+
diff --git a/accessible/windows/msaa/Compatibility.h b/accessible/windows/msaa/Compatibility.h
new file mode 100644
index 000000000..dd9a82f7c
--- /dev/null
+++ b/accessible/windows/msaa/Compatibility.h
@@ -0,0 +1,79 @@
+/* -*- 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 COMPATIBILITY_MANAGER_H
+#define COMPATIBILITY_MANAGER_H
+
+#include <stdint.h>
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Used to get compatibility modes. Note, modes are computed at accessibility
+ * start up time and aren't changed during lifetime.
+ */
+class Compatibility
+{
+public:
+ /**
+ * Return true if IAccessible2 disabled.
+ */
+ static bool IsIA2Off() { return !!(sConsumers & OLDJAWS); }
+
+ /**
+ * Return true if JAWS mode is enabled.
+ */
+ static bool IsJAWS() { return !!(sConsumers & (JAWS | OLDJAWS)); }
+
+ /**
+ * Return true if WE mode is enabled.
+ */
+ static bool IsWE() { return !!(sConsumers & WE); }
+
+ /**
+ * Return true if Dolphin mode is enabled.
+ */
+ static bool IsDolphin() { return !!(sConsumers & DOLPHIN); }
+
+private:
+ Compatibility();
+ Compatibility(const Compatibility&);
+ Compatibility& operator = (const Compatibility&);
+
+ /**
+ * Initialize compatibility mode. Called by platform (see Platform.h) during
+ * accessibility initialization.
+ */
+ static void Init();
+ friend void PlatformInit();
+
+ /**
+ * List of detected consumers of a11y (used for statistics/telemetry and compat)
+ */
+ enum {
+ NVDA = 1 << 0,
+ JAWS = 1 << 1,
+ OLDJAWS = 1 << 2,
+ WE = 1 << 3,
+ DOLPHIN = 1 << 4,
+ SEROTEK = 1 << 5,
+ COBRA = 1 << 6,
+ ZOOMTEXT = 1 << 7,
+ KAZAGURU = 1 << 8,
+ YOUDAO = 1 << 9,
+ UNKNOWN = 1 << 10,
+ UIAUTOMATION = 1 << 11
+ };
+
+private:
+ static uint32_t sConsumers;
+};
+
+} // a11y namespace
+} // mozilla namespace
+
+#endif
diff --git a/accessible/windows/msaa/DocAccessibleWrap.cpp b/accessible/windows/msaa/DocAccessibleWrap.cpp
new file mode 100644
index 000000000..6fb89816d
--- /dev/null
+++ b/accessible/windows/msaa/DocAccessibleWrap.cpp
@@ -0,0 +1,165 @@
+/* -*- 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 "Compatibility.h"
+#include "DocAccessibleChild.h"
+#include "nsWinUtils.h"
+#include "Role.h"
+#include "RootAccessible.h"
+#include "sdnDocAccessible.h"
+#include "Statistics.h"
+
+#include "nsIDocShell.h"
+#include "nsIInterfaceRequestorUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// DocAccessibleWrap
+////////////////////////////////////////////////////////////////////////////////
+
+DocAccessibleWrap::
+ DocAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell) :
+ DocAccessible(aDocument, aPresShell), mHWND(nullptr)
+{
+}
+
+DocAccessibleWrap::~DocAccessibleWrap()
+{
+}
+
+IMPL_IUNKNOWN_QUERY_HEAD(DocAccessibleWrap)
+ if (aIID == IID_ISimpleDOMDocument) {
+ statistics::ISimpleDOMUsed();
+ *aInstancePtr = static_cast<ISimpleDOMDocument*>(new sdnDocAccessible(this));
+ static_cast<IUnknown*>(*aInstancePtr)->AddRef();
+ return S_OK;
+ }
+IMPL_IUNKNOWN_QUERY_TAIL_INHERITED(HyperTextAccessibleWrap)
+
+STDMETHODIMP
+DocAccessibleWrap::get_accParent(
+ /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppdispParent)
+{
+ // We might be a top-level document in a content process.
+ DocAccessibleChild* ipcDoc = IPCDoc();
+ if (!ipcDoc) {
+ return DocAccessible::get_accParent(ppdispParent);
+ }
+ IAccessible* dispParent = ipcDoc->GetParentIAccessible();
+ if (!dispParent) {
+ return S_FALSE;
+ }
+
+ dispParent->AddRef();
+ *ppdispParent = static_cast<IDispatch*>(dispParent);
+ return S_OK;
+}
+
+STDMETHODIMP
+DocAccessibleWrap::get_accValue(VARIANT aVarChild, BSTR __RPC_FAR* aValue)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aValue)
+ return E_INVALIDARG;
+ *aValue = nullptr;
+
+ // For backwards-compat, we still support old MSAA hack to provide URL in accValue
+ // Check for real value first
+ HRESULT hr = AccessibleWrap::get_accValue(aVarChild, aValue);
+ if (FAILED(hr) || *aValue || aVarChild.lVal != CHILDID_SELF)
+ return hr;
+
+ // If document is being used to create a widget, don't use the URL hack
+ roles::Role role = Role();
+ if (role != roles::DOCUMENT && role != roles::APPLICATION &&
+ role != roles::DIALOG && role != roles::ALERT)
+ return hr;
+
+ nsAutoString url;
+ URL(url);
+ if (url.IsEmpty())
+ return S_FALSE;
+
+ *aValue = ::SysAllocStringLen(url.get(), url.Length());
+ return *aValue ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible
+
+void
+DocAccessibleWrap::Shutdown()
+{
+ // Do window emulation specific shutdown if emulation was started.
+ if (nsWinUtils::IsWindowEmulationStarted()) {
+ // Destroy window created for root document.
+ if (mDocFlags & eTabDocument) {
+ HWND hWnd = static_cast<HWND>(mHWND);
+ ::RemovePropW(hWnd, kPropNameDocAcc);
+ ::DestroyWindow(hWnd);
+ }
+
+ mHWND = nullptr;
+ }
+
+ DocAccessible::Shutdown();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DocAccessible public
+
+void*
+DocAccessibleWrap::GetNativeWindow() const
+{
+ return mHWND ? mHWND : DocAccessible::GetNativeWindow();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DocAccessible protected
+
+void
+DocAccessibleWrap::DoInitialUpdate()
+{
+ DocAccessible::DoInitialUpdate();
+
+ if (nsWinUtils::IsWindowEmulationStarted()) {
+ // Create window for tab document.
+ if (mDocFlags & eTabDocument) {
+ a11y::RootAccessible* rootDocument = RootAccessible();
+ bool isActive = true;
+ nsIntRect rect(CW_USEDEFAULT, CW_USEDEFAULT, 0, 0);
+ if (Compatibility::IsDolphin()) {
+ rect = Bounds();
+ nsIntRect rootRect = rootDocument->Bounds();
+ rect.x = rootRect.x - rect.x;
+ rect.y -= rootRect.y;
+
+ nsCOMPtr<nsISupports> container = mDocumentNode->GetContainer();
+ nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(container);
+ docShell->GetIsActive(&isActive);
+ }
+
+ HWND parentWnd = reinterpret_cast<HWND>(rootDocument->GetNativeWindow());
+ mHWND = nsWinUtils::CreateNativeWindow(kClassNameTabContent, parentWnd,
+ rect.x, rect.y,
+ rect.width, rect.height, isActive);
+
+ ::SetPropW(static_cast<HWND>(mHWND), kPropNameDocAcc, (HANDLE)this);
+
+ } else {
+ DocAccessible* parentDocument = ParentDocument();
+ if (parentDocument)
+ mHWND = parentDocument->GetNativeWindow();
+ }
+ }
+}
diff --git a/accessible/windows/msaa/DocAccessibleWrap.h b/accessible/windows/msaa/DocAccessibleWrap.h
new file mode 100644
index 000000000..effa23848
--- /dev/null
+++ b/accessible/windows/msaa/DocAccessibleWrap.h
@@ -0,0 +1,65 @@
+/* -*- 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_DocAccessibleWrap_h__
+#define mozilla_a11y_DocAccessibleWrap_h__
+
+#include "DocAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+class DocAccessibleWrap : public DocAccessible
+{
+public:
+ DocAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell);
+ virtual ~DocAccessibleWrap();
+
+ DECL_IUNKNOWN_INHERITED
+
+ // IAccessible
+
+ // Override get_accParent for e10s
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accParent(
+ /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppdispParent) override;
+
+ // Override get_accValue to provide URL when no other value is available
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_accValue(
+ /* [optional][in] */ VARIANT varChild,
+ /* [retval][out] */ BSTR __RPC_FAR *pszValue) override;
+
+ // Accessible
+ virtual void Shutdown();
+
+ // DocAccessible
+ virtual void* GetNativeWindow() const;
+
+ /**
+ * Manage the mapping from id to Accessible.
+ */
+ void AddID(uint32_t aID, AccessibleWrap* aAcc)
+ { mIDToAccessibleMap.Put(aID, aAcc); }
+ void RemoveID(uint32_t aID) { mIDToAccessibleMap.Remove(aID); }
+ AccessibleWrap* GetAccessibleByID(uint32_t aID) const
+ { return mIDToAccessibleMap.Get(aID); }
+
+protected:
+ // DocAccessible
+ virtual void DoInitialUpdate();
+
+protected:
+ void* mHWND;
+
+ /*
+ * This provides a mapping from 32 bit id to accessible objects.
+ */
+ nsDataHashtable<nsUint32HashKey, AccessibleWrap*> mIDToAccessibleMap;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/msaa/EnumVariant.cpp b/accessible/windows/msaa/EnumVariant.cpp
new file mode 100644
index 000000000..86c81a105
--- /dev/null
+++ b/accessible/windows/msaa/EnumVariant.cpp
@@ -0,0 +1,108 @@
+/* -*- 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 "EnumVariant.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// ChildrenEnumVariant
+////////////////////////////////////////////////////////////////////////////////
+
+IMPL_IUNKNOWN_QUERY_HEAD(ChildrenEnumVariant)
+IMPL_IUNKNOWN_QUERY_IFACE(IEnumVARIANT)
+IMPL_IUNKNOWN_QUERY_TAIL_AGGREGATED(mAnchorAcc)
+
+STDMETHODIMP
+ChildrenEnumVariant::Next(ULONG aCount, VARIANT FAR* aItems,
+ ULONG FAR* aCountFetched)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aItems || !aCountFetched)
+ return E_INVALIDARG;
+
+ *aCountFetched = 0;
+
+ if (mAnchorAcc->IsDefunct() || mAnchorAcc->GetChildAt(mCurIndex) != mCurAcc)
+ return CO_E_OBJNOTCONNECTED;
+
+ ULONG countFetched = 0;
+ while (mCurAcc && countFetched < aCount) {
+ VariantInit(aItems + countFetched);
+
+ IDispatch* accNative = AccessibleWrap::NativeAccessible(mCurAcc);
+
+ ++mCurIndex;
+ mCurAcc = mAnchorAcc->GetChildAt(mCurIndex);
+
+ // Don't output the accessible and count it as having been fetched unless
+ // it is non-null
+ MOZ_ASSERT(accNative);
+ if (!accNative) {
+ continue;
+ }
+
+ aItems[countFetched].pdispVal = accNative;
+ aItems[countFetched].vt = VT_DISPATCH;
+ ++countFetched;
+ }
+
+ (*aCountFetched) = countFetched;
+
+ return countFetched < aCount ? S_FALSE : S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ChildrenEnumVariant::Skip(ULONG aCount)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (mAnchorAcc->IsDefunct() || mAnchorAcc->GetChildAt(mCurIndex) != mCurAcc)
+ return CO_E_OBJNOTCONNECTED;
+
+ mCurIndex += aCount;
+ mCurAcc = mAnchorAcc->GetChildAt(mCurIndex);
+
+ return mCurAcc ? S_OK : S_FALSE;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ChildrenEnumVariant::Reset()
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (mAnchorAcc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ mCurIndex = 0;
+ mCurAcc = mAnchorAcc->GetChildAt(0);
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+ChildrenEnumVariant::Clone(IEnumVARIANT** aEnumVariant)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aEnumVariant)
+ return E_INVALIDARG;
+
+ *aEnumVariant = new ChildrenEnumVariant(*this);
+ (*aEnumVariant)->AddRef();
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
diff --git a/accessible/windows/msaa/EnumVariant.h b/accessible/windows/msaa/EnumVariant.h
new file mode 100644
index 000000000..39e342dd5
--- /dev/null
+++ b/accessible/windows/msaa/EnumVariant.h
@@ -0,0 +1,60 @@
+/* -*- 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_EnumVariant_h__
+#define mozilla_a11y_EnumVariant_h__
+
+#include "AccessibleWrap.h"
+#include "IUnknownImpl.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Used to fetch accessible children.
+ */
+class ChildrenEnumVariant final : public IEnumVARIANT
+{
+public:
+ ChildrenEnumVariant(AccessibleWrap* aAnchor) : mAnchorAcc(aAnchor),
+ mCurAcc(mAnchorAcc->GetChildAt(0)), mCurIndex(0) { }
+
+ // IUnknown
+ DECL_IUNKNOWN
+
+ // IEnumVariant
+ virtual /* [local] */ HRESULT STDMETHODCALLTYPE Next(
+ /* [in] */ ULONG aCount,
+ /* [length_is][size_is][out] */ VARIANT* aItems,
+ /* [out] */ ULONG* aCountFetched);
+
+ virtual HRESULT STDMETHODCALLTYPE Skip(
+ /* [in] */ ULONG aCount);
+
+ virtual HRESULT STDMETHODCALLTYPE Reset();
+
+ virtual HRESULT STDMETHODCALLTYPE Clone(
+ /* [out] */ IEnumVARIANT** aEnumVaraint);
+
+private:
+ ChildrenEnumVariant() = delete;
+ ChildrenEnumVariant& operator =(const ChildrenEnumVariant&) = delete;
+
+ ChildrenEnumVariant(const ChildrenEnumVariant& aEnumVariant) :
+ mAnchorAcc(aEnumVariant.mAnchorAcc), mCurAcc(aEnumVariant.mCurAcc),
+ mCurIndex(aEnumVariant.mCurIndex) { }
+ virtual ~ChildrenEnumVariant() { }
+
+protected:
+ RefPtr<AccessibleWrap> mAnchorAcc;
+ Accessible* mCurAcc;
+ uint32_t mCurIndex;
+};
+
+} // a11y namespace
+} // mozilla namespace
+
+#endif
diff --git a/accessible/windows/msaa/HTMLTableAccessibleWrap.cpp b/accessible/windows/msaa/HTMLTableAccessibleWrap.cpp
new file mode 100644
index 000000000..13cea8853
--- /dev/null
+++ b/accessible/windows/msaa/HTMLTableAccessibleWrap.cpp
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 "HTMLTableAccessibleWrap.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableAccessibleWrap
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS_INHERITED0(HTMLTableAccessibleWrap,
+ HTMLTableAccessible)
+
+IMPL_IUNKNOWN_INHERITED1(HTMLTableAccessibleWrap,
+ AccessibleWrap,
+ ia2AccessibleTable)
+
+void
+HTMLTableAccessibleWrap::Shutdown()
+{
+ ia2AccessibleTable::mTable = nullptr;
+ HTMLTableAccessible::Shutdown();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableCellAccessibleWrap
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS_INHERITED0(HTMLTableCellAccessibleWrap,
+ HTMLTableCellAccessible)
+
+IMPL_IUNKNOWN_INHERITED1(HTMLTableCellAccessibleWrap,
+ HyperTextAccessibleWrap,
+ ia2AccessibleTableCell)
+
+void
+HTMLTableCellAccessibleWrap::Shutdown()
+{
+ ia2AccessibleTableCell::mTableCell = nullptr;
+ HTMLTableCellAccessible::Shutdown();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLTableCellAccessibleWrap
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS_INHERITED0(HTMLTableHeaderCellAccessibleWrap,
+ HTMLTableHeaderCellAccessible)
+
+IMPL_IUNKNOWN_INHERITED1(HTMLTableHeaderCellAccessibleWrap,
+ HyperTextAccessibleWrap,
+ ia2AccessibleTableCell)
+
+void
+HTMLTableHeaderCellAccessibleWrap::Shutdown()
+{
+ ia2AccessibleTableCell::mTableCell = nullptr;
+ HTMLTableHeaderCellAccessible::Shutdown();
+}
diff --git a/accessible/windows/msaa/HTMLTableAccessibleWrap.h b/accessible/windows/msaa/HTMLTableAccessibleWrap.h
new file mode 100644
index 000000000..71d149a3c
--- /dev/null
+++ b/accessible/windows/msaa/HTMLTableAccessibleWrap.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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_HTMLTableAccessibleWrap_h__
+#define mozilla_a11y_HTMLTableAccessibleWrap_h__
+
+#include "HTMLTableAccessible.h"
+
+#include "ia2AccessibleTable.h"
+#include "ia2AccessibleTableCell.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * IA2 wrapper class for HTMLTableAccessible implementing IAccessibleTable
+ * and IAccessibleTable2 interfaces.
+ */
+class HTMLTableAccessibleWrap : public HTMLTableAccessible,
+ public ia2AccessibleTable
+{
+ ~HTMLTableAccessibleWrap() {}
+
+public:
+ HTMLTableAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) :
+ HTMLTableAccessible(aContent, aDoc), ia2AccessibleTable(this) {}
+
+ // IUnknown
+ DECL_IUNKNOWN_INHERITED
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ virtual void Shutdown() override;
+};
+
+
+/**
+ * IA2 wrapper class for HTMLTableCellAccessible implementing
+ * IAccessibleTableCell interface.
+ */
+class HTMLTableCellAccessibleWrap : public HTMLTableCellAccessible,
+ public ia2AccessibleTableCell
+{
+ ~HTMLTableCellAccessibleWrap() {}
+
+public:
+ HTMLTableCellAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) :
+ HTMLTableCellAccessible(aContent, aDoc), ia2AccessibleTableCell(this) {}
+
+ // IUnknown
+ DECL_IUNKNOWN_INHERITED
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ virtual void Shutdown() override;
+};
+
+
+/**
+ * IA2 wrapper class for HTMLTableHeaderCellAccessible implementing
+ * IAccessibleTableCell interface.
+ */
+class HTMLTableHeaderCellAccessibleWrap : public HTMLTableHeaderCellAccessible,
+ public ia2AccessibleTableCell
+{
+ ~HTMLTableHeaderCellAccessibleWrap() {}
+
+public:
+ HTMLTableHeaderCellAccessibleWrap(nsIContent* aContent,
+ DocAccessible* aDoc) :
+ HTMLTableHeaderCellAccessible(aContent, aDoc), ia2AccessibleTableCell(this)
+ {}
+
+ // IUnknown
+ DECL_IUNKNOWN_INHERITED
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ virtual void Shutdown() override;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/windows/msaa/HTMLWin32ObjectAccessible.cpp b/accessible/windows/msaa/HTMLWin32ObjectAccessible.cpp
new file mode 100644
index 000000000..3644db68d
--- /dev/null
+++ b/accessible/windows/msaa/HTMLWin32ObjectAccessible.cpp
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "HTMLWin32ObjectAccessible.h"
+
+#include "Role.h"
+#include "States.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLWin32ObjectOwnerAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLWin32ObjectOwnerAccessible::
+ HTMLWin32ObjectOwnerAccessible(nsIContent* aContent,
+ DocAccessible* aDoc, void* aHwnd) :
+ AccessibleWrap(aContent, aDoc), mHwnd(aHwnd)
+{
+ mStateFlags |= eNoKidsFromDOM;
+
+ // Our only child is a HTMLWin32ObjectAccessible object.
+ if (mHwnd) {
+ mNativeAccessible = new HTMLWin32ObjectAccessible(mHwnd, aDoc);
+ AppendChild(mNativeAccessible);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLWin32ObjectOwnerAccessible: Accessible implementation
+
+void
+HTMLWin32ObjectOwnerAccessible::Shutdown()
+{
+ AccessibleWrap::Shutdown();
+ mNativeAccessible = nullptr;
+}
+
+role
+HTMLWin32ObjectOwnerAccessible::NativeRole()
+{
+ return roles::EMBEDDED_OBJECT;
+}
+
+bool
+HTMLWin32ObjectOwnerAccessible::NativelyUnavailable() const
+{
+ // XXX: No HWND means this is windowless plugin which is not accessible in
+ // the meantime.
+ return !mHwnd;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLWin32ObjectAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLWin32ObjectAccessible::HTMLWin32ObjectAccessible(void* aHwnd,
+ DocAccessible* aDoc) :
+ DummyAccessible(aDoc)
+{
+ mHwnd = aHwnd;
+ if (mHwnd) {
+#if defined(MOZ_CONTENT_SANDBOX)
+ if (XRE_IsContentProcess()) {
+ DocAccessibleChild* ipcDoc = aDoc->IPCDoc();
+ MOZ_ASSERT(ipcDoc);
+ if (!ipcDoc) {
+ return;
+ }
+
+ IAccessibleHolder proxyHolder;
+ if (!ipcDoc->SendGetWindowedPluginIAccessible(
+ reinterpret_cast<uintptr_t>(mHwnd), &proxyHolder)) {
+ return;
+ }
+
+ mCOMProxy.reset(proxyHolder.Release());
+ return;
+ }
+#endif
+
+ // The plugin is not windowless. In this situation we use
+ // use its inner child owned by the plugin so that we don't get
+ // in an infinite loop, where the WM_GETOBJECT's get forwarded
+ // back to us and create another HTMLWin32ObjectAccessible
+ mHwnd = ::GetWindow((HWND)aHwnd, GW_CHILD);
+ }
+}
+
+void
+HTMLWin32ObjectAccessible::GetNativeInterface(void** aNativeAccessible)
+{
+#if defined(MOZ_CONTENT_SANDBOX)
+ if (XRE_IsContentProcess()) {
+ RefPtr<IAccessible> addRefed = mCOMProxy.get();
+ addRefed.forget(aNativeAccessible);
+ return;
+ }
+#endif
+
+ if (mHwnd) {
+ ::AccessibleObjectFromWindow(static_cast<HWND>(mHwnd),
+ OBJID_WINDOW, IID_IAccessible,
+ aNativeAccessible);
+ }
+}
+
diff --git a/accessible/windows/msaa/HTMLWin32ObjectAccessible.h b/accessible/windows/msaa/HTMLWin32ObjectAccessible.h
new file mode 100644
index 000000000..786d52191
--- /dev/null
+++ b/accessible/windows/msaa/HTMLWin32ObjectAccessible.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_HTMLWin32ObjectAccessible_h_
+#define mozilla_a11y_HTMLWin32ObjectAccessible_h_
+
+#include "BaseAccessibles.h"
+
+#if defined(MOZ_CONTENT_SANDBOX)
+#include "mozilla/mscom/Ptr.h"
+#endif
+
+struct IAccessible;
+
+namespace mozilla {
+namespace a11y {
+
+class HTMLWin32ObjectOwnerAccessible : public AccessibleWrap
+{
+public:
+ // This will own the HTMLWin32ObjectAccessible. We create this where the
+ // <object> or <embed> exists in the tree, so that get_accNextSibling() etc.
+ // will still point to Gecko accessible sibling content. This is necessary
+ // because the native plugin accessible doesn't know where it exists in the
+ // Mozilla tree, and returns null for previous and next sibling. This would
+ // have the effect of cutting off all content after the plugin.
+ HTMLWin32ObjectOwnerAccessible(nsIContent* aContent,
+ DocAccessible* aDoc, void* aHwnd);
+ virtual ~HTMLWin32ObjectOwnerAccessible() {}
+
+ // Accessible
+ virtual void Shutdown();
+ virtual mozilla::a11y::role NativeRole();
+ virtual bool NativelyUnavailable() const;
+
+protected:
+ void* mHwnd;
+ RefPtr<Accessible> mNativeAccessible;
+};
+
+/**
+ * This class is used only internally, we never! send out an IAccessible linked
+ * back to this object. This class is used to represent a plugin object when
+ * referenced as a child or sibling of another Accessible node. We need only
+ * a limited portion of the Accessible interface implemented here. The
+ * in depth accessible information will be returned by the actual IAccessible
+ * object returned by us in Accessible::NewAccessible() that gets the IAccessible
+ * from the windows system from the window handle.
+ */
+class HTMLWin32ObjectAccessible : public DummyAccessible
+{
+public:
+ HTMLWin32ObjectAccessible(void* aHwnd, DocAccessible* aDoc);
+ virtual ~HTMLWin32ObjectAccessible() {}
+
+ virtual void GetNativeInterface(void** aNativeAccessible) override;
+
+protected:
+ void* mHwnd;
+#if defined(MOZ_CONTENT_SANDBOX)
+ mscom::ProxyUniquePtr<IAccessible> mCOMProxy;
+#endif
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/msaa/HyperTextAccessibleWrap.cpp b/accessible/windows/msaa/HyperTextAccessibleWrap.cpp
new file mode 100644
index 000000000..b5fd716d9
--- /dev/null
+++ b/accessible/windows/msaa/HyperTextAccessibleWrap.cpp
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 "HyperTextAccessibleWrap.h"
+#include "Accessible-inl.h"
+
+#include "nsEventShell.h"
+
+#include "mozilla/StaticPtr.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+NS_IMPL_ISUPPORTS_INHERITED0(HyperTextAccessibleWrap,
+ HyperTextAccessible)
+
+STDMETHODIMP
+HyperTextAccessibleWrap::QueryInterface(REFIID aIID, void** aInstancePtr)
+{
+ if (!aInstancePtr)
+ return E_FAIL;
+
+ *aInstancePtr = nullptr;
+
+ if (IsTextRole()) {
+ if (aIID == IID_IAccessibleText)
+ *aInstancePtr =
+ static_cast<IAccessibleText*>(static_cast<ia2AccessibleText*>(this));
+ else if (aIID == IID_IAccessibleHypertext)
+ *aInstancePtr = static_cast<IAccessibleHypertext*>(this);
+ else if (aIID == IID_IAccessibleEditableText)
+ *aInstancePtr = static_cast<IAccessibleEditableText*>(this);
+
+ if (*aInstancePtr) {
+ AddRef();
+ return S_OK;
+ }
+ }
+
+ return AccessibleWrap::QueryInterface(aIID, aInstancePtr);
+}
+
+nsresult
+HyperTextAccessibleWrap::HandleAccEvent(AccEvent* aEvent)
+{
+ uint32_t eventType = aEvent->GetEventType();
+
+ if (eventType == nsIAccessibleEvent::EVENT_TEXT_REMOVED ||
+ eventType == nsIAccessibleEvent::EVENT_TEXT_INSERTED) {
+ Accessible* accessible = aEvent->GetAccessible();
+ if (accessible && accessible->IsHyperText()) {
+ AccTextChangeEvent* event = downcast_accEvent(aEvent);
+ HyperTextAccessibleWrap* text =
+ static_cast<HyperTextAccessibleWrap*>(accessible->AsHyperText());
+ ia2AccessibleText::UpdateTextChangeData(text, event->IsTextInserted(),
+ event->ModifiedText(),
+ event->GetStartOffset(),
+ event->GetLength());
+ }
+ }
+
+ return HyperTextAccessible::HandleAccEvent(aEvent);
+}
diff --git a/accessible/windows/msaa/HyperTextAccessibleWrap.h b/accessible/windows/msaa/HyperTextAccessibleWrap.h
new file mode 100644
index 000000000..c0f853da2
--- /dev/null
+++ b/accessible/windows/msaa/HyperTextAccessibleWrap.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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_HyperTextAccessibleWrap_h__
+#define mozilla_a11y_HyperTextAccessibleWrap_h__
+
+#include "HyperTextAccessible.h"
+#include "ia2AccessibleEditableText.h"
+#include "ia2AccessibleHypertext.h"
+#include "IUnknownImpl.h"
+
+namespace mozilla {
+template<class T> class StaticAutoPtr;
+template<class T> class StaticRefPtr;
+
+namespace a11y {
+
+class HyperTextAccessibleWrap : public HyperTextAccessible,
+ public ia2AccessibleHypertext,
+ public ia2AccessibleEditableText
+{
+public:
+ HyperTextAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) :
+ HyperTextAccessible(aContent, aDoc) {}
+
+ // IUnknown
+ DECL_IUNKNOWN_INHERITED
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual nsresult HandleAccEvent(AccEvent* aEvent);
+
+protected:
+ ~HyperTextAccessibleWrap() {}
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/msaa/IDSet.h b/accessible/windows/msaa/IDSet.h
new file mode 100644
index 000000000..3c3ed74c3
--- /dev/null
+++ b/accessible/windows/msaa/IDSet.h
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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/. */
+
+/**
+ * A class to generate unique IDs in the range [ - 2^31, 0 )
+ */
+
+#ifndef MOZILLA_A11Y_IDSet_h_
+#define MOZILLA_A11Y_IDSet_h_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/SplayTree.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * On windows an accessible's id must be a negative 32 bit integer. It is
+ * important to support recycling arbitrary IDs because accessibles can be
+ * created and destroyed at any time in the life of a page. IDSet provides 2
+ * operations: generate an ID in the range (0, mMaxId], and release an ID so
+ * it can be allocated again. Allocated ID are tracked by a sparse bitmap
+ * implemented with a splay tree. Nodes in the tree are keyed by the upper N
+ * bits of the ID, and the node contains a bitmap tracking the allocation of
+ * 2^(ceil(log2(mMaxId)) - N) IDs.
+ *
+ * Note that negation is handled by MsaaIdGenerator as it performs additional
+ * decoration on the ID generated by IDSet.
+ * @see mozilla::a11y::MsaaIdGenerator
+ */
+class IDSet
+{
+public:
+ constexpr explicit IDSet(const uint32_t aMaxIdBits)
+ : mBitSet()
+ , mIdx(0)
+ , mMaxId((1UL << aMaxIdBits) - 1UL)
+ , mMaxIdx(mMaxId / bitsPerElt)
+ {
+ }
+
+ /**
+ * Return a new unique id.
+ */
+ uint32_t GetID()
+ {
+ uint32_t idx = mIdx;
+ while (true) {
+ BitSetElt* elt = mBitSet.findOrInsert(BitSetElt(idx));
+ if (elt->mBitvec[0] != UINT64_MAX) {
+ uint32_t i = CountTrailingZeroes64(~elt->mBitvec[0]);
+
+ elt->mBitvec[0] |= (1ull << i);
+ mIdx = idx;
+ return (elt->mIdx * bitsPerElt + i);
+ }
+
+ if (elt->mBitvec[1] != UINT64_MAX) {
+ uint32_t i = CountTrailingZeroes64(~elt->mBitvec[1]);
+
+ elt->mBitvec[1] |= (1ull << i);
+ mIdx = idx;
+ return (elt->mIdx * bitsPerElt + bitsPerWord + i);
+ }
+
+ idx++;
+ if (idx > mMaxIdx) {
+ idx = 0;
+ }
+
+ if (idx == mIdx) {
+ MOZ_CRASH("used up all the available ids");
+ }
+ }
+ }
+
+ /**
+ * Free a no longer required id so it may be allocated again.
+ */
+ void ReleaseID(uint32_t aID)
+ {
+ MOZ_ASSERT(aID < mMaxId);
+
+ uint32_t idx = aID / bitsPerElt;
+ mIdx = idx;
+ BitSetElt* elt = mBitSet.find(BitSetElt(idx));
+ MOZ_ASSERT(elt);
+
+ uint32_t vecIdx = (aID % bitsPerElt) / bitsPerWord;
+ elt->mBitvec[vecIdx] &= ~(1ull << (aID % bitsPerWord));
+ if (elt->mBitvec[0] == 0 && elt->mBitvec[1] == 0) {
+ delete mBitSet.remove(*elt);
+ }
+ }
+
+private:
+ static const unsigned int wordsPerElt = 2;
+ static const unsigned int bitsPerWord = 64;
+ static const unsigned int bitsPerElt = wordsPerElt * bitsPerWord;
+
+ struct BitSetElt : mozilla::SplayTreeNode<BitSetElt>
+ {
+ explicit BitSetElt(uint32_t aIdx) :
+ mIdx(aIdx)
+ { mBitvec[0] = mBitvec[1] = 0; }
+
+ uint64_t mBitvec[wordsPerElt];
+ uint32_t mIdx;
+
+ static int compare(const BitSetElt& a, const BitSetElt& b)
+ {
+ if (a.mIdx == b.mIdx) {
+ return 0;
+ }
+
+ if (a.mIdx < b.mIdx) {
+ return -1;
+ }
+ return 1;
+ }
+ };
+
+ SplayTree<BitSetElt, BitSetElt> mBitSet;
+ uint32_t mIdx;
+ const uint32_t mMaxId;
+ const uint32_t mMaxIdx;
+};
+
+}
+}
+
+#endif
diff --git a/accessible/windows/msaa/IUnknownImpl.cpp b/accessible/windows/msaa/IUnknownImpl.cpp
new file mode 100644
index 000000000..4a9fa5383
--- /dev/null
+++ b/accessible/windows/msaa/IUnknownImpl.cpp
@@ -0,0 +1,58 @@
+/* -*- 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 "IUnknownImpl.h"
+
+#include "nsDebug.h"
+
+#ifdef MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
+#endif
+
+namespace mozilla {
+namespace a11y {
+
+HRESULT
+GetHRESULT(nsresult aResult)
+{
+ switch (aResult) {
+ case NS_OK:
+ return S_OK;
+
+ case NS_ERROR_INVALID_ARG:
+ return E_INVALIDARG;
+
+ case NS_ERROR_OUT_OF_MEMORY:
+ return E_OUTOFMEMORY;
+
+ case NS_ERROR_NOT_IMPLEMENTED:
+ return E_NOTIMPL;
+
+ default:
+ return E_FAIL;
+ }
+}
+
+int
+FilterExceptions(unsigned int aCode, EXCEPTION_POINTERS* aExceptionInfo)
+{
+ if (aCode == EXCEPTION_ACCESS_VIOLATION) {
+#ifdef MOZ_CRASHREPORTER
+ // MSAA swallows crashes (because it is COM-based) but we still need to
+ // learn about those crashes so we can fix them. Make sure to pass them to
+ // the crash reporter.
+ CrashReporter::WriteMinidumpForException(aExceptionInfo);
+#endif
+ } else {
+ NS_NOTREACHED("We should only be catching crash exceptions");
+ }
+
+ return EXCEPTION_CONTINUE_SEARCH;
+}
+
+} // namespace a11y
+} // namespace mozilla
diff --git a/accessible/windows/msaa/IUnknownImpl.h b/accessible/windows/msaa/IUnknownImpl.h
new file mode 100644
index 000000000..dbf6c1374
--- /dev/null
+++ b/accessible/windows/msaa/IUnknownImpl.h
@@ -0,0 +1,192 @@
+/* -*- 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_IUnknownImpl_h_
+#define mozilla_a11y_IUnknownImpl_h_
+
+#include <windows.h>
+#undef CreateEvent // thank you windows you're such a helper
+#include "nsError.h"
+
+// Avoid warning C4509 like "nonstandard extension used:
+// 'AccessibleWrap::[acc_getName]' uses SEH and 'name' has destructor.
+// At this point we're catching a crash which is of much greater
+// importance than the missing dereference for the nsCOMPtr<>
+#ifdef _MSC_VER
+#pragma warning( disable : 4509 )
+#endif
+
+#ifdef __GNUC__
+#define ATTRIBUTE_UNUSED __attribute__((unused))
+#else
+#define ATTRIBUTE_UNUSED
+#endif
+
+namespace mozilla {
+namespace a11y {
+
+class AutoRefCnt
+{
+public:
+ AutoRefCnt() : mValue(0) {}
+
+ ULONG operator++() { return ++mValue; }
+ ULONG operator--() { return --mValue; }
+ ULONG operator++(int) { return ++mValue; }
+ ULONG operator--(int) { return --mValue; }
+
+ operator ULONG() const { return mValue; }
+
+private:
+ ULONG mValue;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#define DECL_IUNKNOWN \
+public: \
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, void**); \
+ virtual ULONG STDMETHODCALLTYPE AddRef() final \
+ { \
+ MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \
+ ++mRefCnt; \
+ return mRefCnt; \
+ } \
+ virtual ULONG STDMETHODCALLTYPE Release() final \
+ { \
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \
+ --mRefCnt; \
+ if (mRefCnt) \
+ return mRefCnt; \
+ \
+ delete this; \
+ return 0; \
+ } \
+private: \
+ mozilla::a11y::AutoRefCnt mRefCnt; \
+public:
+
+#define DECL_IUNKNOWN_INHERITED \
+public: \
+virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, void**); \
+
+#define IMPL_IUNKNOWN_QUERY_HEAD(Class) \
+STDMETHODIMP \
+Class::QueryInterface(REFIID aIID, void** aInstancePtr) \
+{ \
+ A11Y_TRYBLOCK_BEGIN \
+ if (!aInstancePtr) \
+ return E_INVALIDARG; \
+ *aInstancePtr = nullptr; \
+ \
+ HRESULT hr ATTRIBUTE_UNUSED = E_NOINTERFACE;
+
+#define IMPL_IUNKNOWN_QUERY_TAIL \
+ return hr; \
+ A11Y_TRYBLOCK_END \
+}
+
+#define IMPL_IUNKNOWN_QUERY_TAIL_AGGREGATED(Member) \
+ return Member->QueryInterface(aIID, aInstancePtr); \
+ A11Y_TRYBLOCK_END \
+}
+
+#define IMPL_IUNKNOWN_QUERY_TAIL_INHERITED(BaseClass) \
+ return BaseClass::QueryInterface(aIID, aInstancePtr); \
+ A11Y_TRYBLOCK_END \
+}
+
+#define IMPL_IUNKNOWN_QUERY_IFACE(Iface) \
+ if (aIID == IID_##Iface) { \
+ *aInstancePtr = static_cast<Iface*>(this); \
+ AddRef(); \
+ return S_OK; \
+ }
+
+#define IMPL_IUNKNOWN_QUERY_IFACE_AMBIGIOUS(Iface, aResolveIface) \
+ if (aIID == IID_##Iface) { \
+ *aInstancePtr = static_cast<Iface*>(static_cast<aResolveIface*>(this)); \
+ AddRef(); \
+ return S_OK; \
+ }
+
+#define IMPL_IUNKNOWN_QUERY_CLASS(Class) \
+ hr = Class::QueryInterface(aIID, aInstancePtr); \
+ if (SUCCEEDED(hr)) \
+ return hr;
+
+#define IMPL_IUNKNOWN_QUERY_CLASS_COND(Class, Cond) \
+ if (Cond) { \
+ hr = Class::QueryInterface(aIID, aInstancePtr); \
+ if (SUCCEEDED(hr)) \
+ return hr; \
+ }
+
+#define IMPL_IUNKNOWN_QUERY_AGGR_COND(Member, Cond) \
+ if (Cond) { \
+ hr = Member->QueryInterface(aIID, aInstancePtr); \
+ if (SUCCEEDED(hr)) \
+ return hr; \
+ }
+
+#define IMPL_IUNKNOWN1(Class, I1) \
+ IMPL_IUNKNOWN_QUERY_HEAD(Class) \
+ IMPL_IUNKNOWN_QUERY_IFACE(I1); \
+ IMPL_IUNKNOWN_QUERY_IFACE(IUnknown); \
+ IMPL_IUNKNOWN_QUERY_TAIL \
+
+#define IMPL_IUNKNOWN2(Class, I1, I2) \
+ IMPL_IUNKNOWN_QUERY_HEAD(Class) \
+ IMPL_IUNKNOWN_QUERY_IFACE(I1); \
+ IMPL_IUNKNOWN_QUERY_IFACE(I2); \
+ IMPL_IUNKNOWN_QUERY_IFACE_AMBIGIOUS(IUnknown, I1); \
+ IMPL_IUNKNOWN_QUERY_TAIL \
+
+#define IMPL_IUNKNOWN_INHERITED1(Class, Super0, Super1) \
+ IMPL_IUNKNOWN_QUERY_HEAD(Class) \
+ IMPL_IUNKNOWN_QUERY_CLASS(Super1); \
+ IMPL_IUNKNOWN_QUERY_TAIL_INHERITED(Super0)
+
+#define IMPL_IUNKNOWN_INHERITED2(Class, Super0, Super1, Super2) \
+ IMPL_IUNKNOWN_QUERY_HEAD(Class) \
+ IMPL_IUNKNOWN_QUERY_CLASS(Super1); \
+ IMPL_IUNKNOWN_QUERY_CLASS(Super2); \
+ IMPL_IUNKNOWN_QUERY_TAIL_INHERITED(Super0)
+
+
+/**
+ * Wrap every method body by these macroses to pass exception to the crash
+ * reporter.
+ */
+#define A11Y_TRYBLOCK_BEGIN \
+ MOZ_SEH_TRY {
+
+#define A11Y_TRYBLOCK_END \
+ } MOZ_SEH_EXCEPT(mozilla::a11y::FilterExceptions(::GetExceptionCode(), \
+ GetExceptionInformation())) \
+ { } \
+ return E_FAIL;
+
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Converts nsresult to HRESULT.
+ */
+HRESULT GetHRESULT(nsresult aResult);
+
+/**
+ * Used to pass an exception to the crash reporter.
+ */
+int FilterExceptions(unsigned int aCode, EXCEPTION_POINTERS* aExceptionInfo);
+
+} // namespace a11y;
+} //namespace mozilla;
+
+#endif
diff --git a/accessible/windows/msaa/ImageAccessibleWrap.cpp b/accessible/windows/msaa/ImageAccessibleWrap.cpp
new file mode 100644
index 000000000..7ff2e8a47
--- /dev/null
+++ b/accessible/windows/msaa/ImageAccessibleWrap.cpp
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 "ImageAccessibleWrap.h"
+#include "nsIURI.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+NS_IMPL_ISUPPORTS_INHERITED0(ImageAccessibleWrap,
+ ImageAccessible)
+
+IMPL_IUNKNOWN_INHERITED1(ImageAccessibleWrap,
+ AccessibleWrap,
+ ia2AccessibleImage)
+
diff --git a/accessible/windows/msaa/ImageAccessibleWrap.h b/accessible/windows/msaa/ImageAccessibleWrap.h
new file mode 100644
index 000000000..e6a916ebe
--- /dev/null
+++ b/accessible/windows/msaa/ImageAccessibleWrap.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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_ImageAccessibleWrap_h__
+#define mozilla_a11y_ImageAccessibleWrap_h__
+
+#include "ImageAccessible.h"
+#include "ia2AccessibleImage.h"
+
+namespace mozilla {
+namespace a11y {
+
+class ImageAccessibleWrap : public ImageAccessible,
+ public ia2AccessibleImage
+{
+public:
+ ImageAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) :
+ ImageAccessible(aContent, aDoc) {}
+
+ // IUnknown
+ DECL_IUNKNOWN_INHERITED
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+protected:
+ ~ImageAccessibleWrap() {}
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/windows/msaa/MsaaIdGenerator.cpp b/accessible/windows/msaa/MsaaIdGenerator.cpp
new file mode 100644
index 000000000..5f4b333fa
--- /dev/null
+++ b/accessible/windows/msaa/MsaaIdGenerator.cpp
@@ -0,0 +1,243 @@
+/* -*- 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 "MsaaIdGenerator.h"
+
+#include "mozilla/a11y/AccessibleWrap.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Unused.h"
+#include "nsDataHashtable.h"
+#include "nsIXULRuntime.h"
+
+// These constants may be adjusted to modify the proportion of the Child ID
+// allocated to the content ID vs proportion allocated to the unique ID. They
+// must always sum to 31, ie. the width of a 32-bit integer less the sign bit.
+
+// NB: kNumContentProcessIDBits must be large enough to successfully hold the
+// maximum permitted number of e10s content processes. If the e10s maximum
+// number of content processes changes, then kNumContentProcessIDBits must also
+// be updated if necessary to accommodate that new value!
+static const uint32_t kNumContentProcessIDBits = 7UL;
+static const uint32_t kNumUniqueIDBits = (31UL - kNumContentProcessIDBits);
+
+static_assert(kNumContentProcessIDBits + kNumUniqueIDBits == 31,
+ "Allocation of Content ID bits and Unique ID bits must sum to 31");
+
+namespace mozilla {
+namespace a11y {
+namespace detail {
+
+typedef nsDataHashtable<nsUint64HashKey, uint32_t> ContentParentIdMap;
+
+#pragma pack(push, 1)
+union MsaaID
+{
+ int32_t mInt32;
+ uint32_t mUInt32;
+ struct
+ {
+ uint32_t mUniqueID:kNumUniqueIDBits;
+ uint32_t mContentProcessID:kNumContentProcessIDBits;
+ uint32_t mSignBit:1;
+ }
+ mCracked;
+};
+#pragma pack(pop)
+
+static uint32_t
+BuildMsaaID(const uint32_t aID, const uint32_t aContentProcessID)
+{
+ MsaaID id;
+ id.mCracked.mSignBit = 0;
+ id.mCracked.mUniqueID = aID;
+ id.mCracked.mContentProcessID = aContentProcessID;
+ return ~id.mUInt32;
+}
+
+class MsaaIDCracker
+{
+public:
+ explicit MsaaIDCracker(const uint32_t aMsaaID)
+ {
+ mID.mUInt32 = ~aMsaaID;
+ }
+
+ uint32_t GetContentProcessId()
+ {
+ return mID.mCracked.mContentProcessID;
+ }
+
+ uint32_t GetUniqueId()
+ {
+ return mID.mCracked.mUniqueID;
+ }
+
+private:
+ MsaaID mID;
+};
+
+} // namespace detail
+
+constexpr MsaaIdGenerator::MsaaIdGenerator()
+ : mIDSet(kNumUniqueIDBits)
+{}
+
+uint32_t
+MsaaIdGenerator::GetID()
+{
+ uint32_t id = mIDSet.GetID();
+ MOZ_ASSERT(id <= ((1UL << kNumUniqueIDBits) - 1UL));
+ return detail::BuildMsaaID(id, ResolveContentProcessID());
+}
+
+void
+MsaaIdGenerator::ReleaseID(AccessibleWrap* aAccWrap)
+{
+ MOZ_ASSERT(aAccWrap);
+ uint32_t id = aAccWrap->GetExistingID();
+ MOZ_ASSERT(id != AccessibleWrap::kNoID);
+ detail::MsaaIDCracker cracked(id);
+ if (cracked.GetContentProcessId() != ResolveContentProcessID()) {
+ // This may happen if chrome holds a proxy whose ID was originally generated
+ // by a content process. Since ReleaseID only has meaning in the process
+ // that originally generated that ID, we ignore ReleaseID calls for any ID
+ // that did not come from the current process.
+ MOZ_ASSERT(aAccWrap->IsProxy());
+ return;
+ }
+ mIDSet.ReleaseID(cracked.GetUniqueId());
+}
+
+bool
+MsaaIdGenerator::IsChromeID(uint32_t aID)
+{
+ detail::MsaaIDCracker cracked(aID);
+ return cracked.GetContentProcessId() == 0;
+}
+
+bool
+MsaaIdGenerator::IsIDForThisContentProcess(uint32_t aID)
+{
+ MOZ_ASSERT(XRE_IsContentProcess());
+ detail::MsaaIDCracker cracked(aID);
+ return cracked.GetContentProcessId() == ResolveContentProcessID();
+}
+
+bool
+MsaaIdGenerator::IsIDForContentProcess(uint32_t aID,
+ dom::ContentParentId aIPCContentProcessId)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ detail::MsaaIDCracker cracked(aID);
+ return cracked.GetContentProcessId() ==
+ GetContentProcessIDFor(aIPCContentProcessId);
+}
+
+bool
+MsaaIdGenerator::IsSameContentProcessFor(uint32_t aFirstID, uint32_t aSecondID)
+{
+ detail::MsaaIDCracker firstCracked(aFirstID);
+ detail::MsaaIDCracker secondCracked(aSecondID);
+ return firstCracked.GetContentProcessId() ==
+ secondCracked.GetContentProcessId();
+}
+
+uint32_t
+MsaaIdGenerator::ResolveContentProcessID()
+{
+ if (XRE_IsParentProcess()) {
+ return 0;
+ }
+
+ dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
+ uint32_t result = contentChild->GetMsaaID();
+
+ MOZ_ASSERT(result);
+ return result;
+}
+
+/**
+ * Each dom::ContentParent has a 64-bit ID. This ID is monotonically increasing
+ * with each new content process, so those IDs are effectively single-use. OTOH,
+ * MSAA requires 32-bit IDs. Since we only allocate kNumContentProcessIDBits for
+ * the content process ID component, the MSAA content process ID value must be
+ * reusable. sContentParentIdMap holds the current associations between
+ * dom::ContentParent IDs and the MSAA content parent IDs that have been
+ * allocated to them.
+ */
+static StaticAutoPtr<detail::ContentParentIdMap> sContentParentIdMap;
+
+static const uint32_t kBitsPerByte = 8UL;
+// Set sContentProcessIdBitmap[0] to 1 to reserve the Chrome process's id
+static uint64_t sContentProcessIdBitmap[(1UL << kNumContentProcessIDBits) /
+ (sizeof(uint64_t) * kBitsPerByte)] = {1ULL};
+static const uint32_t kBitsPerElement = sizeof(sContentProcessIdBitmap[0]) *
+ kBitsPerByte;
+
+uint32_t
+MsaaIdGenerator::GetContentProcessIDFor(dom::ContentParentId aIPCContentProcessID)
+{
+ MOZ_ASSERT(XRE_IsParentProcess() && NS_IsMainThread());
+ if (!sContentParentIdMap) {
+ sContentParentIdMap = new detail::ContentParentIdMap();
+ ClearOnShutdown(&sContentParentIdMap);
+ }
+
+ uint32_t value = 0;
+ if (sContentParentIdMap->Get(aIPCContentProcessID, &value)) {
+ return value;
+ }
+
+ uint32_t index = 0;
+ for (; index < ArrayLength(sContentProcessIdBitmap); ++index) {
+ if (sContentProcessIdBitmap[index] == UINT64_MAX) {
+ continue;
+ }
+ uint32_t bitIndex = CountTrailingZeroes64(~sContentProcessIdBitmap[index]);
+ MOZ_ASSERT(!(sContentProcessIdBitmap[index] & (1ULL << bitIndex)));
+ MOZ_ASSERT(bitIndex != 0 || index != 0);
+ sContentProcessIdBitmap[index] |= (1ULL << bitIndex);
+ value = index * kBitsPerElement + bitIndex;
+ break;
+ }
+
+ // If we run out of content process IDs, we're in trouble
+ MOZ_RELEASE_ASSERT(index < ArrayLength(sContentProcessIdBitmap));
+
+ sContentParentIdMap->Put(aIPCContentProcessID, value);
+ return value;
+}
+
+void
+MsaaIdGenerator::ReleaseContentProcessIDFor(dom::ContentParentId aIPCContentProcessID)
+{
+ MOZ_ASSERT(XRE_IsParentProcess() && NS_IsMainThread());
+ if (!sContentParentIdMap) {
+ // Since Content IDs are generated lazily, ContentParent might attempt
+ // to release an ID that was never allocated to begin with.
+ return;
+ }
+
+ Maybe<uint32_t> mapping = sContentParentIdMap->GetAndRemove(aIPCContentProcessID);
+ if (!mapping) {
+ // Since Content IDs are generated lazily, ContentParent might attempt
+ // to release an ID that was never allocated to begin with.
+ return;
+ }
+
+ uint32_t index = mapping.ref() / kBitsPerElement;
+ MOZ_ASSERT(index < ArrayLength(sContentProcessIdBitmap));
+
+ uint64_t mask = 1ULL << (mapping.ref() % kBitsPerElement);
+ MOZ_ASSERT(sContentProcessIdBitmap[index] & mask);
+
+ sContentProcessIdBitmap[index] &= ~mask;
+}
+
+} // namespace a11y
+} // namespace mozilla
diff --git a/accessible/windows/msaa/MsaaIdGenerator.h b/accessible/windows/msaa/MsaaIdGenerator.h
new file mode 100644
index 000000000..b845e8473
--- /dev/null
+++ b/accessible/windows/msaa/MsaaIdGenerator.h
@@ -0,0 +1,56 @@
+/* -*- 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_MsaaIdGenerator_h
+#define mozilla_a11y_MsaaIdGenerator_h
+
+#include "mozilla/a11y/IDSet.h"
+
+#include "mozilla/dom/ipc/IdType.h"
+
+namespace mozilla {
+namespace a11y {
+
+class AccessibleWrap;
+
+/**
+ * This class is responsible for generating child IDs used by our MSAA
+ * implementation. Since e10s requires us to differentiate IDs based on the
+ * originating process of the accessible, a portion of the ID's bits are
+ * allocated to storing that information. The remaining bits represent the
+ * unique ID of the accessible, within that content process.
+ *
+ * The constants kNumContentProcessIDBits and kNumUniqueIDBits in the
+ * implementation are responsible for determining the proportion of bits that
+ * are allocated for each purpose.
+ */
+class MsaaIdGenerator
+{
+public:
+ constexpr MsaaIdGenerator();
+
+ uint32_t GetID();
+ void ReleaseID(AccessibleWrap* aAccWrap);
+ bool IsChromeID(uint32_t aID);
+ bool IsIDForThisContentProcess(uint32_t aID);
+ bool IsIDForContentProcess(uint32_t aID,
+ dom::ContentParentId aIPCContentProcessId);
+ bool IsSameContentProcessFor(uint32_t aFirstID, uint32_t aSecondID);
+
+ uint32_t GetContentProcessIDFor(dom::ContentParentId aIPCContentProcessID);
+ void ReleaseContentProcessIDFor(dom::ContentParentId aIPCContentProcessID);
+
+private:
+ uint32_t ResolveContentProcessID();
+
+private:
+ IDSet mIDSet;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_MsaaIdGenerator_h
diff --git a/accessible/windows/msaa/Platform.cpp b/accessible/windows/msaa/Platform.cpp
new file mode 100644
index 000000000..dc6acd3ad
--- /dev/null
+++ b/accessible/windows/msaa/Platform.cpp
@@ -0,0 +1,147 @@
+/* -*- 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 "AccEvent.h"
+#include "Compatibility.h"
+#include "HyperTextAccessibleWrap.h"
+#include "ia2AccessibleText.h"
+#include "nsIXULRuntime.h"
+#include "nsWinUtils.h"
+#include "mozilla/a11y/ProxyAccessible.h"
+#include "mozilla/mscom/InterceptorLog.h"
+#include "mozilla/mscom/Registration.h"
+#include "mozilla/StaticPtr.h"
+#include "ProxyWrappers.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+using namespace mozilla::mscom;
+
+static StaticAutoPtr<RegisteredProxy> gRegProxy;
+static StaticAutoPtr<RegisteredProxy> gRegAccTlb;
+static StaticAutoPtr<RegisteredProxy> gRegMiscTlb;
+
+void
+a11y::PlatformInit()
+{
+ Compatibility::Init();
+
+ nsWinUtils::MaybeStartWindowEmulation();
+ ia2AccessibleText::InitTextChangeData();
+ if (BrowserTabsRemoteAutostart()) {
+ mscom::InterceptorLog::Init();
+ UniquePtr<RegisteredProxy> regProxy(
+ mscom::RegisterProxy(L"ia2marshal.dll"));
+ gRegProxy = regProxy.release();
+ UniquePtr<RegisteredProxy> regAccTlb(
+ mscom::RegisterTypelib(L"oleacc.dll",
+ RegistrationFlags::eUseSystemDirectory));
+ gRegAccTlb = regAccTlb.release();
+ UniquePtr<RegisteredProxy> regMiscTlb(
+ mscom::RegisterTypelib(L"Accessible.tlb"));
+ gRegMiscTlb = regMiscTlb.release();
+ }
+}
+
+void
+a11y::PlatformShutdown()
+{
+ ::DestroyCaret();
+
+ nsWinUtils::ShutdownWindowEmulation();
+ gRegProxy = nullptr;
+ gRegAccTlb = nullptr;
+ gRegMiscTlb = nullptr;
+}
+
+void
+a11y::ProxyCreated(ProxyAccessible* aProxy, uint32_t aInterfaces)
+{
+ AccessibleWrap* wrapper = nullptr;
+ if (aInterfaces & Interfaces::DOCUMENT) {
+ wrapper = new DocProxyAccessibleWrap(aProxy);
+ } else if (aInterfaces & Interfaces::HYPERTEXT) {
+ wrapper = new HyperTextProxyAccessibleWrap(aProxy);
+ } else {
+ wrapper = new ProxyAccessibleWrap(aProxy);
+ }
+
+ wrapper->SetProxyInterfaces(aInterfaces);
+ wrapper->AddRef();
+ aProxy->SetWrapper(reinterpret_cast<uintptr_t>(wrapper));
+}
+
+void
+a11y::ProxyDestroyed(ProxyAccessible* aProxy)
+{
+ AccessibleWrap* wrapper =
+ reinterpret_cast<AccessibleWrap*>(aProxy->GetWrapper());
+ MOZ_ASSERT(wrapper);
+ if (!wrapper)
+ return;
+
+ wrapper->Shutdown();
+ aProxy->SetWrapper(0);
+ wrapper->Release();
+}
+
+void
+a11y::ProxyEvent(ProxyAccessible* aTarget, uint32_t aEventType)
+{
+ AccessibleWrap::FireWinEvent(WrapperFor(aTarget), aEventType);
+}
+
+void
+a11y::ProxyStateChangeEvent(ProxyAccessible* aTarget, uint64_t, bool)
+{
+ AccessibleWrap::FireWinEvent(WrapperFor(aTarget),
+ nsIAccessibleEvent::EVENT_STATE_CHANGE);
+}
+
+void
+a11y::ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset)
+{
+ AccessibleWrap::FireWinEvent(WrapperFor(aTarget),
+ nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED);
+}
+
+void
+a11y::ProxyTextChangeEvent(ProxyAccessible* aText, const nsString& aStr,
+ int32_t aStart, uint32_t aLen, bool aInsert, bool)
+{
+ AccessibleWrap* wrapper = WrapperFor(aText);
+ MOZ_ASSERT(wrapper);
+ if (!wrapper) {
+ return;
+ }
+
+ auto text = static_cast<HyperTextAccessibleWrap*>(wrapper->AsHyperText());
+ if (text) {
+ ia2AccessibleText::UpdateTextChangeData(text, aInsert, aStr, aStart, aLen);
+ }
+
+ uint32_t eventType = aInsert ? nsIAccessibleEvent::EVENT_TEXT_INSERTED :
+ nsIAccessibleEvent::EVENT_TEXT_REMOVED;
+ AccessibleWrap::FireWinEvent(wrapper, eventType);
+}
+
+void
+a11y::ProxyShowHideEvent(ProxyAccessible* aTarget, ProxyAccessible*, bool aInsert, bool)
+{
+ uint32_t event = aInsert ? nsIAccessibleEvent::EVENT_SHOW :
+ nsIAccessibleEvent::EVENT_HIDE;
+ AccessibleWrap* wrapper = WrapperFor(aTarget);
+ AccessibleWrap::FireWinEvent(wrapper, event);
+}
+
+void
+a11y::ProxySelectionEvent(ProxyAccessible* aTarget, ProxyAccessible*, uint32_t aType)
+{
+ AccessibleWrap* wrapper = WrapperFor(aTarget);
+ AccessibleWrap::FireWinEvent(wrapper, aType);
+}
diff --git a/accessible/windows/msaa/RootAccessibleWrap.cpp b/accessible/windows/msaa/RootAccessibleWrap.cpp
new file mode 100644
index 000000000..30fe40c43
--- /dev/null
+++ b/accessible/windows/msaa/RootAccessibleWrap.cpp
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "Compatibility.h"
+#include "nsCoreUtils.h"
+#include "nsWinUtils.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// Constructor/destructor
+
+RootAccessibleWrap::
+ RootAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell) :
+ RootAccessible(aDocument, aPresShell)
+{
+}
+
+RootAccessibleWrap::~RootAccessibleWrap()
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// RootAccessible
+
+void
+RootAccessibleWrap::DocumentActivated(DocAccessible* aDocument)
+{
+ if (Compatibility::IsDolphin() &&
+ nsCoreUtils::IsTabDocument(aDocument->DocumentNode())) {
+ uint32_t count = mChildDocuments.Length();
+ for (uint32_t idx = 0; idx < count; idx++) {
+ DocAccessible* childDoc = mChildDocuments[idx];
+ HWND childDocHWND = static_cast<HWND>(childDoc->GetNativeWindow());
+ if (childDoc != aDocument)
+ nsWinUtils::HideNativeWindow(childDocHWND);
+ else
+ nsWinUtils::ShowNativeWindow(childDocHWND);
+ }
+ }
+}
diff --git a/accessible/windows/msaa/RootAccessibleWrap.h b/accessible/windows/msaa/RootAccessibleWrap.h
new file mode 100644
index 000000000..6aa6fefe3
--- /dev/null
+++ b/accessible/windows/msaa/RootAccessibleWrap.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "RootAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+class RootAccessibleWrap : public RootAccessible
+{
+public:
+ RootAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell);
+ virtual ~RootAccessibleWrap();
+
+ // RootAccessible
+ virtual void DocumentActivated(DocAccessible* aDocument);
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/msaa/ServiceProvider.cpp b/accessible/windows/msaa/ServiceProvider.cpp
new file mode 100644
index 000000000..82265d3c2
--- /dev/null
+++ b/accessible/windows/msaa/ServiceProvider.cpp
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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 "ServiceProvider.h"
+
+#include "ApplicationAccessibleWrap.h"
+#include "Compatibility.h"
+#include "DocAccessible.h"
+#include "nsAccUtils.h"
+#include "nsCoreUtils.h"
+#include "Relation.h"
+#include "uiaRawElmProvider.h"
+
+#include "mozilla/Preferences.h"
+#include "nsIDocShell.h"
+
+#include "ISimpleDOMNode_i.c"
+
+namespace mozilla {
+namespace a11y {
+
+IMPL_IUNKNOWN_QUERY_HEAD(ServiceProvider)
+ IMPL_IUNKNOWN_QUERY_IFACE(IServiceProvider)
+IMPL_IUNKNOWN_QUERY_TAIL_AGGREGATED(mAccessible)
+
+
+////////////////////////////////////////////////////////////////////////////////
+// IServiceProvider
+
+STDMETHODIMP
+ServiceProvider::QueryService(REFGUID aGuidService, REFIID aIID,
+ void** aInstancePtr)
+{
+ if (!aInstancePtr)
+ return E_INVALIDARG;
+
+ *aInstancePtr = nullptr;
+
+ // UIA IAccessibleEx
+ if (aGuidService == IID_IAccessibleEx &&
+ Preferences::GetBool("accessibility.uia.enable")) {
+ uiaRawElmProvider* accEx = new uiaRawElmProvider(mAccessible);
+ HRESULT hr = accEx->QueryInterface(aIID, aInstancePtr);
+ if (FAILED(hr))
+ delete accEx;
+
+ return hr;
+ }
+
+ // Provide a special service ID for getting the accessible for the browser tab
+ // document that contains this accessible object. If this accessible object
+ // is not inside a browser tab then the service fails with E_NOINTERFACE.
+ // A use case for this is for screen readers that need to switch context or
+ // 'virtual buffer' when focus moves from one browser tab area to another.
+ static const GUID SID_IAccessibleContentDocument =
+ { 0xa5d8e1f3,0x3571,0x4d8f,{0x95,0x21,0x07,0xed,0x28,0xfb,0x07,0x2e} };
+ if (aGuidService == SID_IAccessibleContentDocument) {
+ if (aIID != IID_IAccessible)
+ return E_NOINTERFACE;
+
+ Relation rel = mAccessible->RelationByType(RelationType::CONTAINING_TAB_PANE);
+ AccessibleWrap* tabDoc = static_cast<AccessibleWrap*>(rel.Next());
+ if (!tabDoc)
+ return E_NOINTERFACE;
+
+ *aInstancePtr = static_cast<IAccessible*>(tabDoc);
+ (reinterpret_cast<IUnknown*>(*aInstancePtr))->AddRef();
+ return S_OK;
+ }
+
+ // Can get to IAccessibleApplication from any node via QS
+ if (aGuidService == IID_IAccessibleApplication ||
+ (Compatibility::IsJAWS() && aIID == IID_IAccessibleApplication)) {
+ ApplicationAccessibleWrap* applicationAcc =
+ static_cast<ApplicationAccessibleWrap*>(ApplicationAcc());
+ if (!applicationAcc)
+ return E_NOINTERFACE;
+
+ return applicationAcc->QueryInterface(aIID, aInstancePtr);
+ }
+
+ static const GUID IID_SimpleDOMDeprecated =
+ { 0x0c539790,0x12e4,0x11cf,{0xb6,0x61,0x00,0xaa,0x00,0x4c,0xd6,0xd8} };
+ if (aGuidService == IID_ISimpleDOMNode ||
+ aGuidService == IID_SimpleDOMDeprecated ||
+ aGuidService == IID_IAccessible || aGuidService == IID_IAccessible2)
+ return mAccessible->QueryInterface(aIID, aInstancePtr);
+
+ return E_INVALIDARG;
+}
+
+} // namespace a11y
+} // namespace mozilla
diff --git a/accessible/windows/msaa/ServiceProvider.h b/accessible/windows/msaa/ServiceProvider.h
new file mode 100644
index 000000000..b0fc812c5
--- /dev/null
+++ b/accessible/windows/msaa/ServiceProvider.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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_ServiceProvider_h_
+#define mozilla_a11y_ServiceProvider_h_
+
+#include <servprov.h>
+
+#include "AccessibleWrap.h"
+#include "IUnknownImpl.h"
+
+namespace mozilla {
+namespace a11y {
+
+class ServiceProvider final : public IServiceProvider
+{
+public:
+ ServiceProvider(AccessibleWrap* aAcc) : mAccessible(aAcc) {}
+ ~ServiceProvider() {}
+
+ DECL_IUNKNOWN
+
+ // IServiceProvider
+ virtual HRESULT STDMETHODCALLTYPE QueryService(REFGUID aGuidService,
+ REFIID aIID,
+ void** aInstancePtr);
+
+private:
+ RefPtr<AccessibleWrap> mAccessible;
+};
+
+}
+}
+
+#endif
diff --git a/accessible/windows/msaa/TextLeafAccessibleWrap.cpp b/accessible/windows/msaa/TextLeafAccessibleWrap.cpp
new file mode 100644
index 000000000..6f1d193db
--- /dev/null
+++ b/accessible/windows/msaa/TextLeafAccessibleWrap.cpp
@@ -0,0 +1,21 @@
+/* -*- 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 "TextLeafAccessibleWrap.h"
+
+#include "sdnTextAccessible.h"
+#include "Statistics.h"
+
+using namespace mozilla::a11y;
+
+IMPL_IUNKNOWN_QUERY_HEAD(TextLeafAccessibleWrap)
+ if (aIID == IID_ISimpleDOMText) {
+ statistics::ISimpleDOMUsed();
+ *aInstancePtr = static_cast<ISimpleDOMText*>(new sdnTextAccessible(this));
+ static_cast<IUnknown*>(*aInstancePtr)->AddRef();
+ return S_OK;
+ }
+IMPL_IUNKNOWN_QUERY_TAIL_INHERITED(AccessibleWrap)
diff --git a/accessible/windows/msaa/TextLeafAccessibleWrap.h b/accessible/windows/msaa/TextLeafAccessibleWrap.h
new file mode 100644
index 000000000..612bed173
--- /dev/null
+++ b/accessible/windows/msaa/TextLeafAccessibleWrap.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/. */
+
+#ifndef mozilla_a11y_TextLeafAccessibleWrap_h__
+#define mozilla_a11y_TextLeafAccessibleWrap_h__
+
+#include "TextLeafAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Wrap TextLeafAccessible to expose ISimpleDOMText as a native interface with
+ * a tear off.
+ */
+class TextLeafAccessibleWrap : public TextLeafAccessible
+{
+public:
+ TextLeafAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) :
+ TextLeafAccessible(aContent, aDoc) { }
+ virtual ~TextLeafAccessibleWrap() {}
+
+ // IUnknown
+ DECL_IUNKNOWN_INHERITED
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/msaa/XULListboxAccessibleWrap.cpp b/accessible/windows/msaa/XULListboxAccessibleWrap.cpp
new file mode 100644
index 000000000..4bd0fb512
--- /dev/null
+++ b/accessible/windows/msaa/XULListboxAccessibleWrap.cpp
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "XULListboxAccessibleWrap.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// XULListboxAccessibleWrap
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS_INHERITED0(XULListboxAccessibleWrap,
+ XULListboxAccessible)
+
+IMPL_IUNKNOWN_QUERY_HEAD(XULListboxAccessibleWrap)
+IMPL_IUNKNOWN_QUERY_CLASS_COND(ia2AccessibleTable,
+ !IsDefunct() && IsMulticolumn());
+IMPL_IUNKNOWN_QUERY_CLASS(AccessibleWrap)
+IMPL_IUNKNOWN_QUERY_TAIL
+
+void
+XULListboxAccessibleWrap::Shutdown()
+{
+ ia2AccessibleTable::mTable = nullptr;
+ XULListboxAccessible::Shutdown();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULListCellAccessibleWrap
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS_INHERITED0(XULListCellAccessibleWrap,
+ XULListCellAccessible)
+
+IMPL_IUNKNOWN_INHERITED1(XULListCellAccessibleWrap,
+ HyperTextAccessibleWrap,
+ ia2AccessibleTableCell)
+
+void
+XULListCellAccessibleWrap::Shutdown()
+{
+ ia2AccessibleTableCell::mTableCell = nullptr;
+ XULListCellAccessible::Shutdown();
+}
diff --git a/accessible/windows/msaa/XULListboxAccessibleWrap.h b/accessible/windows/msaa/XULListboxAccessibleWrap.h
new file mode 100644
index 000000000..37db2d70a
--- /dev/null
+++ b/accessible/windows/msaa/XULListboxAccessibleWrap.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_XULListboxAccessibleWrap_h__
+#define mozilla_a11y_XULListboxAccessibleWrap_h__
+
+#include "XULListboxAccessible.h"
+
+#include "ia2AccessibleTable.h"
+#include "ia2AccessibleTableCell.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * IA2 wrapper class for XULListboxAccessible class implementing
+ * IAccessibleTable and IAccessibleTable2 interfaces.
+ */
+class XULListboxAccessibleWrap : public XULListboxAccessible,
+ public ia2AccessibleTable
+{
+ ~XULListboxAccessibleWrap() {}
+
+public:
+ XULListboxAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) :
+ XULListboxAccessible(aContent, aDoc), ia2AccessibleTable(this) {}
+
+ // IUnknown
+ DECL_IUNKNOWN_INHERITED
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ virtual void Shutdown() override;
+};
+
+/**
+ * IA2 wrapper class for XULListCellAccessible class, implements
+ * IAccessibleTableCell interface.
+ */
+class XULListCellAccessibleWrap : public XULListCellAccessible,
+ public ia2AccessibleTableCell
+{
+ ~XULListCellAccessibleWrap() {}
+
+public:
+ XULListCellAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) :
+ XULListCellAccessible(aContent, aDoc), ia2AccessibleTableCell(this) {}
+
+ // IUnknown
+ DECL_IUNKNOWN_INHERITED
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ virtual void Shutdown() override;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/msaa/XULMenuAccessibleWrap.cpp b/accessible/windows/msaa/XULMenuAccessibleWrap.cpp
new file mode 100644
index 000000000..ba0075bac
--- /dev/null
+++ b/accessible/windows/msaa/XULMenuAccessibleWrap.cpp
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "XULMenuAccessibleWrap.h"
+#include "nsNameSpaceManager.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// XULMenuAccessibleWrap
+////////////////////////////////////////////////////////////////////////////////
+
+XULMenuitemAccessibleWrap::
+ XULMenuitemAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) :
+ XULMenuitemAccessible(aContent, aDoc)
+{
+}
+
+ENameValueFlag
+XULMenuitemAccessibleWrap::Name(nsString& aName)
+{
+ // XXX This should be done in MSAA's get_accName() so that Accessible::Name()]
+ // provides the same results on all platforms
+ XULMenuitemAccessible::Name(aName);
+ if (aName.IsEmpty())
+ return eNameOK;
+
+ nsAutoString accel;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::acceltext, accel);
+ if (!accel.IsEmpty())
+ aName += NS_LITERAL_STRING("\t") + accel;
+
+ return eNameOK;
+}
diff --git a/accessible/windows/msaa/XULMenuAccessibleWrap.h b/accessible/windows/msaa/XULMenuAccessibleWrap.h
new file mode 100644
index 000000000..a07182241
--- /dev/null
+++ b/accessible/windows/msaa/XULMenuAccessibleWrap.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_XULMenuAccessibleWrap_h__
+#define mozilla_a11y_XULMenuAccessibleWrap_h__
+
+#include "XULMenuAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+class XULMenuitemAccessibleWrap : public XULMenuitemAccessible
+{
+public:
+ XULMenuitemAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc);
+ virtual ~XULMenuitemAccessibleWrap() {}
+
+ // nsIAccessible
+ virtual mozilla::a11y::ENameValueFlag Name(nsString& aName);
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/msaa/XULTreeGridAccessibleWrap.cpp b/accessible/windows/msaa/XULTreeGridAccessibleWrap.cpp
new file mode 100644
index 000000000..e5cc4a09b
--- /dev/null
+++ b/accessible/windows/msaa/XULTreeGridAccessibleWrap.cpp
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "XULTreeGridAccessibleWrap.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeGridAccessibleWrap
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS_INHERITED0(XULTreeGridAccessibleWrap,
+ XULTreeGridAccessible)
+
+IMPL_IUNKNOWN_INHERITED1(XULTreeGridAccessibleWrap,
+ AccessibleWrap,
+ ia2AccessibleTable)
+
+void
+XULTreeGridAccessibleWrap::Shutdown()
+{
+ ia2AccessibleTable::mTable = nullptr;
+ XULTreeGridAccessible::Shutdown();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeGridCellAccessibleWrap
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS_INHERITED0(XULTreeGridCellAccessibleWrap,
+ XULTreeGridCellAccessible)
+
+IMPL_IUNKNOWN_INHERITED1(XULTreeGridCellAccessibleWrap,
+ AccessibleWrap,
+ ia2AccessibleTableCell)
+
+void
+XULTreeGridCellAccessibleWrap::Shutdown()
+{
+ ia2AccessibleTableCell::mTableCell = nullptr;
+ XULTreeGridCellAccessible::Shutdown();
+}
diff --git a/accessible/windows/msaa/XULTreeGridAccessibleWrap.h b/accessible/windows/msaa/XULTreeGridAccessibleWrap.h
new file mode 100644
index 000000000..3c6c62699
--- /dev/null
+++ b/accessible/windows/msaa/XULTreeGridAccessibleWrap.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_XULTreeGridAccessibleWrap_h__
+#define mozilla_a11y_XULTreeGridAccessibleWrap_h__
+
+#include "XULTreeGridAccessible.h"
+
+#include "ia2AccessibleTable.h"
+#include "ia2AccessibleTableCell.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * IA2 wrapper class for XULTreeGridAccessible class implementing
+ * IAccessibleTable and IAccessibleTable2 interfaces.
+ */
+class XULTreeGridAccessibleWrap : public XULTreeGridAccessible,
+ public ia2AccessibleTable
+{
+ ~XULTreeGridAccessibleWrap() {}
+
+public:
+ XULTreeGridAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc,
+ nsTreeBodyFrame* aTree) :
+ XULTreeGridAccessible(aContent, aDoc, aTree), ia2AccessibleTable(this) {}
+
+ // IUnknown
+ DECL_IUNKNOWN_INHERITED
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ virtual void Shutdown() override;
+};
+
+/**
+ * IA2 wrapper class for XULTreeGridCellAccessible class, implements
+ * IAccessibleTableCell interface.
+ */
+class XULTreeGridCellAccessibleWrap : public XULTreeGridCellAccessible,
+ public ia2AccessibleTableCell
+{
+ ~XULTreeGridCellAccessibleWrap() {}
+
+public:
+ XULTreeGridCellAccessibleWrap(nsIContent* aContent,
+ DocAccessible* aDoc,
+ XULTreeGridRowAccessible* aRowAcc,
+ nsITreeBoxObject* aTree,
+ nsITreeView* aTreeView,
+ int32_t aRow, nsITreeColumn* aColumn) :
+ XULTreeGridCellAccessible(aContent, aDoc, aRowAcc, aTree, aTreeView, aRow,
+ aColumn), ia2AccessibleTableCell(this) {}
+
+ // IUnknown
+ DECL_IUNKNOWN_INHERITED
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ virtual void Shutdown() override;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/msaa/moz.build b/accessible/windows/msaa/moz.build
new file mode 100644
index 000000000..54c8c6686
--- /dev/null
+++ b/accessible/windows/msaa/moz.build
@@ -0,0 +1,76 @@
+# -*- 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 += [
+ 'IUnknownImpl.h',
+]
+
+EXPORTS.mozilla.a11y += [
+ 'AccessibleWrap.h',
+ 'Compatibility.h',
+ 'HyperTextAccessibleWrap.h',
+ 'IDSet.h',
+ 'MsaaIdGenerator.h',
+]
+
+UNIFIED_SOURCES += [
+ 'AccessibleWrap.cpp',
+ 'ApplicationAccessibleWrap.cpp',
+ 'ARIAGridAccessibleWrap.cpp',
+ 'Compatibility.cpp',
+ 'DocAccessibleWrap.cpp',
+ 'EnumVariant.cpp',
+ 'HTMLTableAccessibleWrap.cpp',
+ 'HTMLWin32ObjectAccessible.cpp',
+ 'HyperTextAccessibleWrap.cpp',
+ 'ImageAccessibleWrap.cpp',
+ 'IUnknownImpl.cpp',
+ 'MsaaIdGenerator.cpp',
+ 'nsWinUtils.cpp',
+ 'Platform.cpp',
+ 'RootAccessibleWrap.cpp',
+ 'TextLeafAccessibleWrap.cpp',
+]
+
+# This file cannot be built in unified mode because it includes ISimpleDOMNode_i.c.
+SOURCES += [
+ 'ServiceProvider.cpp',
+]
+
+if CONFIG['MOZ_XUL']:
+ UNIFIED_SOURCES += [
+ 'XULListboxAccessibleWrap.cpp',
+ 'XULMenuAccessibleWrap.cpp',
+ 'XULTreeGridAccessibleWrap.cpp',
+ ]
+
+LOCAL_INCLUDES += [
+ '/accessible/base',
+ '/accessible/generic',
+ '/accessible/html',
+ '/accessible/ipc',
+ '/accessible/ipc/win',
+ '/accessible/windows',
+ '/accessible/windows/ia2',
+ '/accessible/windows/sdn',
+ '/accessible/windows/uia',
+ '/accessible/xpcom',
+ '/accessible/xul',
+ '/dom/base',
+ '/layout/style',
+]
+
+# The Windows MIDL code generator creates things like:
+#
+# #endif !_MIDL_USE_GUIDDEF_
+#
+# which clang-cl complains about. MSVC doesn't, so turn this warning off.
+if CONFIG['CLANG_CL']:
+ CXXFLAGS += ['-Wno-extra-tokens']
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
diff --git a/accessible/windows/msaa/nsEventMap.h b/accessible/windows/msaa/nsEventMap.h
new file mode 100644
index 000000000..8af992e1c
--- /dev/null
+++ b/accessible/windows/msaa/nsEventMap.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* 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 <winuser.h>
+#include "AccessibleEventId.h"
+
+const uint32_t kEVENT_WIN_UNKNOWN = 0x00000000;
+
+static const uint32_t gWinEventMap[] = {
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent doesn't have 0 constant
+ EVENT_OBJECT_SHOW, // nsIAccessibleEvent::EVENT_SHOW
+ EVENT_OBJECT_HIDE, // nsIAccessibleEvent::EVENT_HIDE
+ EVENT_OBJECT_REORDER, // nsIAccessibleEvent::EVENT_REORDER
+ IA2_EVENT_ACTIVE_DECENDENT_CHANGED, // nsIAccessibleEvent::EVENT_ACTIVE_DECENDENT_CHANGED
+ EVENT_OBJECT_FOCUS, // nsIAccessibleEvent::EVENT_FOCUS
+ EVENT_OBJECT_STATECHANGE, // nsIAccessibleEvent::EVENT_STATE_CHANGE
+ EVENT_OBJECT_LOCATIONCHANGE, // nsIAccessibleEvent::EVENT_LOCATION_CHANGE
+ EVENT_OBJECT_NAMECHANGE, // nsIAccessibleEvent::EVENT_NAME_CHANGE
+ EVENT_OBJECT_DESCRIPTIONCHANGE, // nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE
+ EVENT_OBJECT_VALUECHANGE, // nsIAccessibleEvent::EVENT_VALUE_CHANGE
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_HELP_CHANGE
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_DEFACTION_CHANGE
+ IA2_EVENT_ACTION_CHANGED, // nsIAccessibleEvent::EVENT_ACTION_CHANGE
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_ACCELERATOR_CHANGE
+ EVENT_OBJECT_SELECTION, // nsIAccessibleEvent::EVENT_SELECTION
+ EVENT_OBJECT_SELECTIONADD, // nsIAccessibleEvent::EVENT_SELECTION_ADD
+ EVENT_OBJECT_SELECTIONREMOVE, // nsIAccessibleEvent::EVENT_SELECTION_REMOVE
+ EVENT_OBJECT_SELECTIONWITHIN, // nsIAccessibleEvent::EVENT_SELECTION_WITHIN
+ EVENT_SYSTEM_ALERT, // nsIAccessibleEvent::EVENT_ALERT
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_FOREGROUND
+ EVENT_SYSTEM_MENUSTART, // nsIAccessibleEvent::EVENT_MENU_START
+ EVENT_SYSTEM_MENUEND, // nsIAccessibleEvent::EVENT_MENU_END
+ EVENT_SYSTEM_MENUPOPUPSTART, // nsIAccessibleEvent::EVENT_MENUPOPUP_START
+ EVENT_SYSTEM_MENUPOPUPEND, // nsIAccessibleEvent::EVENT_MENUPOPUP_END
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_CAPTURE_START
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_CAPTURE_END
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_MOVESIZE_START
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_MOVESIZE_END
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_CONTEXT_HELP_START
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_CONTEXT_HELP_END
+ EVENT_SYSTEM_DRAGDROPSTART, // nsIAccessibleEvent::EVENT_DRAGDROP_START
+ EVENT_SYSTEM_DRAGDROPEND, // nsIAccessibleEvent::EVENT_DRAGDROP_END
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_DIALOG_START
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_DIALOG_END
+ EVENT_SYSTEM_SCROLLINGSTART, // nsIAccessibleEvent::EVENT_SCROLLING_START
+ EVENT_SYSTEM_SCROLLINGEND, // nsIAccessibleEvent::EVENT_SCROLLING_END
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_MINIMIZE_START
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_MINIMIZE_END
+ IA2_EVENT_DOCUMENT_LOAD_COMPLETE, // nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE
+ IA2_EVENT_DOCUMENT_RELOAD, // nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD
+ IA2_EVENT_DOCUMENT_LOAD_STOPPED, // nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED
+ IA2_EVENT_DOCUMENT_ATTRIBUTE_CHANGED, // nsIAccessibleEvent::EVENT_DOCUMENT_ATTRIBUTES_CHANGED
+ IA2_EVENT_DOCUMENT_CONTENT_CHANGED, // nsIAccessibleEvent::EVENT_DOCUMENT_CONTENT_CHANGED
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_PROPERTY_CHANGED
+ IA2_EVENT_PAGE_CHANGED, // nsIAccessibleEvent::IA2_EVENT_PAGE_CHANGED
+ IA2_EVENT_TEXT_ATTRIBUTE_CHANGED, // nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED
+ IA2_EVENT_TEXT_CARET_MOVED, // nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED
+ IA2_EVENT_TEXT_CHANGED, // nsIAccessibleEvent::EVENT_TEXT_CHANGED
+ IA2_EVENT_TEXT_INSERTED, // nsIAccessibleEvent::EVENT_TEXT_INSERTED
+ IA2_EVENT_TEXT_REMOVED, // nsIAccessibleEvent::EVENT_TEXT_REMOVED
+ IA2_EVENT_TEXT_UPDATED, // nsIAccessibleEvent::EVENT_TEXT_UPDATED
+ IA2_EVENT_TEXT_SELECTION_CHANGED, // nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED
+ IA2_EVENT_VISIBLE_DATA_CHANGED, // nsIAccessibleEvent::EVENT_VISIBLE_DATA_CHANGED
+ IA2_EVENT_TEXT_COLUMN_CHANGED, // nsIAccessibleEvent::EVENT_TEXT_COLUMN_CHANGED
+ IA2_EVENT_SECTION_CHANGED, // nsIAccessibleEvent::EVENT_SECTION_CHANGED
+ IA2_EVENT_TABLE_CAPTION_CHANGED, // nsIAccessibleEvent::EVENT_TABLE_CAPTION_CHANGED
+ IA2_EVENT_TABLE_MODEL_CHANGED, // nsIAccessibleEvent::EVENT_TABLE_MODEL_CHANGED
+ IA2_EVENT_TABLE_SUMMARY_CHANGED, // nsIAccessibleEvent::EVENT_TABLE_SUMMARY_CHANGED
+ IA2_EVENT_TABLE_ROW_DESCRIPTION_CHANGED, // nsIAccessibleEvent::EVENT_TABLE_ROW_DESCRIPTION_CHANGED
+ IA2_EVENT_TABLE_ROW_HEADER_CHANGED, // nsIAccessibleEvent::EVENT_TABLE_ROW_HEADER_CHANGED
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_TABLE_ROW_INSERT
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_TABLE_ROW_DELETE
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_TABLE_ROW_REORDER
+ IA2_EVENT_TABLE_COLUMN_DESCRIPTION_CHANGED, // nsIAccessibleEvent::EVENT_TABLE_COLUMN_DESCRIPTION_CHANGED
+ IA2_EVENT_TABLE_COLUMN_HEADER_CHANGED, // nsIAccessibleEvent::EVENT_TABLE_COLUMN_HEADER_CHANGED
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_TABLE_COLUMN_INSERT
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_TABLE_COLUMN_DELETE
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_TABLE_COLUMN_REORDER
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_WINDOW_ACTIVATE
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_WINDOW_CREATE
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_WINDOW_DEACTIVATE
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_WINDOW_DESTROY
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_WINDOW_MAXIMIZE
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_WINDOW_MINIMIZE
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_WINDOW_RESIZE
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_WINDOW_RESTORE
+ IA2_EVENT_HYPERLINK_END_INDEX_CHANGED, // nsIAccessibleEvent::EVENT_HYPERLINK_END_INDEX_CHANGED
+ IA2_EVENT_HYPERLINK_NUMBER_OF_ANCHORS_CHANGED, // nsIAccessibleEvent::EVENT_HYPERLINK_NUMBER_OF_ANCHORS_CHANGED
+ IA2_EVENT_HYPERLINK_SELECTED_LINK_CHANGED, // nsIAccessibleEvent::EVENT_HYPERLINK_SELECTED_LINK_CHANGED
+ IA2_EVENT_HYPERTEXT_LINK_ACTIVATED, // nsIAccessibleEvent::EVENT_HYPERTEXT_LINK_ACTIVATED
+ IA2_EVENT_HYPERTEXT_LINK_SELECTED, // nsIAccessibleEvent::EVENT_HYPERTEXT_LINK_SELECTED
+ IA2_EVENT_HYPERLINK_START_INDEX_CHANGED, // nsIAccessibleEvent::EVENT_HYPERLINK_START_INDEX_CHANGED
+ IA2_EVENT_HYPERTEXT_CHANGED, // nsIAccessibleEvent::EVENT_HYPERTEXT_CHANGED
+ IA2_EVENT_HYPERTEXT_NLINKS_CHANGED, // nsIAccessibleEvent::EVENT_HYPERTEXT_NLINKS_CHANGED
+ IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED, // nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED
+ kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED
+ EVENT_OBJECT_VALUECHANGE // nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE
+};
+
diff --git a/accessible/windows/msaa/nsWinUtils.cpp b/accessible/windows/msaa/nsWinUtils.cpp
new file mode 100644
index 000000000..b49cd0263
--- /dev/null
+++ b/accessible/windows/msaa/nsWinUtils.cpp
@@ -0,0 +1,181 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 "nsWinUtils.h"
+
+#include "Compatibility.h"
+#include "DocAccessible.h"
+#include "nsAccessibilityService.h"
+#include "nsCoreUtils.h"
+
+#include "mozilla/Preferences.h"
+#include "nsArrayUtils.h"
+#include "nsIArray.h"
+#include "nsICSSDeclaration.h"
+#include "nsIDocument.h"
+#include "nsIDocShellTreeItem.h"
+#include "mozilla/dom/Element.h"
+#include "nsXULAppAPI.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+using mozilla::dom::Element;
+
+// Window property used by ipc related code in identifying accessible
+// tab windows.
+const wchar_t* kPropNameTabContent = L"AccessibleTabWindow";
+
+/**
+ * WindowProc to process WM_GETOBJECT messages, used in windows emulation mode.
+ */
+static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg,
+ WPARAM wParam, LPARAM lParam);
+
+bool nsWinUtils::sWindowEmulationStarted = false;
+
+already_AddRefed<nsIDOMCSSStyleDeclaration>
+nsWinUtils::GetComputedStyleDeclaration(nsIContent* aContent)
+{
+ nsIContent* elm = nsCoreUtils::GetDOMElementFor(aContent);
+ if (!elm)
+ return nullptr;
+
+ // Returns number of items in style declaration
+ nsCOMPtr<nsPIDOMWindowInner> window = elm->OwnerDoc()->GetInnerWindow();
+ if (!window)
+ return nullptr;
+
+ ErrorResult dummy;
+ nsCOMPtr<nsICSSDeclaration> cssDecl;
+ nsCOMPtr<Element> domElement(do_QueryInterface(elm));
+ cssDecl = window->GetComputedStyle(*domElement, EmptyString(), dummy);
+ nsCOMPtr<nsIDOMCSSStyleDeclaration> domDecl = do_QueryInterface(cssDecl);
+ dummy.SuppressException();
+ return domDecl.forget();
+}
+
+bool
+nsWinUtils::MaybeStartWindowEmulation()
+{
+ // Register window class that'll be used for document accessibles associated
+ // with tabs.
+ if (IPCAccessibilityActive())
+ return false;
+
+ if (Compatibility::IsJAWS() || Compatibility::IsWE() ||
+ Compatibility::IsDolphin() ||
+ XRE_IsContentProcess()) {
+ RegisterNativeWindow(kClassNameTabContent);
+ sWindowEmulationStarted = true;
+ return true;
+ }
+
+ return false;
+}
+
+void
+nsWinUtils::ShutdownWindowEmulation()
+{
+ // Unregister window call that's used for document accessibles associated
+ // with tabs.
+ if (IsWindowEmulationStarted()) {
+ ::UnregisterClassW(kClassNameTabContent, GetModuleHandle(nullptr));
+ sWindowEmulationStarted = false;
+ }
+}
+
+void
+nsWinUtils::RegisterNativeWindow(LPCWSTR aWindowClass)
+{
+ WNDCLASSW wc;
+ wc.style = CS_GLOBALCLASS;
+ wc.lpfnWndProc = WindowProc;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ wc.hInstance = GetModuleHandle(nullptr);
+ wc.hIcon = nullptr;
+ wc.hCursor = nullptr;
+ wc.hbrBackground = nullptr;
+ wc.lpszMenuName = nullptr;
+ wc.lpszClassName = aWindowClass;
+ ::RegisterClassW(&wc);
+}
+
+HWND
+nsWinUtils::CreateNativeWindow(LPCWSTR aWindowClass, HWND aParentWnd,
+ int aX, int aY, int aWidth, int aHeight,
+ bool aIsActive)
+{
+ HWND hwnd = ::CreateWindowExW(WS_EX_TRANSPARENT, aWindowClass,
+ L"NetscapeDispatchWnd",
+ WS_CHILD | (aIsActive ? WS_VISIBLE : 0),
+ aX, aY, aWidth, aHeight,
+ aParentWnd,
+ nullptr,
+ GetModuleHandle(nullptr),
+ nullptr);
+ if (hwnd) {
+ // Mark this window so that ipc related code can identify it.
+ ::SetPropW(hwnd, kPropNameTabContent, (HANDLE)1);
+ }
+ return hwnd;
+}
+
+void
+nsWinUtils::ShowNativeWindow(HWND aWnd)
+{
+ ::ShowWindow(aWnd, SW_SHOW);
+}
+
+void
+nsWinUtils::HideNativeWindow(HWND aWnd)
+{
+ ::SetWindowPos(aWnd, nullptr, 0, 0, 0, 0,
+ SWP_HIDEWINDOW | SWP_NOSIZE | SWP_NOMOVE |
+ SWP_NOZORDER | SWP_NOACTIVATE);
+}
+
+LRESULT CALLBACK
+WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ // Note, this window's message handling should not invoke any call that
+ // may result in a cross-process ipc call. Doing so may violate RPC
+ // message semantics.
+
+ switch (msg) {
+ case WM_GETOBJECT:
+ {
+ // Do explicit casting to make it working on 64bit systems (see bug 649236
+ // for details).
+ int32_t objId = static_cast<DWORD>(lParam);
+ if (objId == OBJID_CLIENT) {
+ DocAccessible* document =
+ reinterpret_cast<DocAccessible*>(::GetPropW(hWnd, kPropNameDocAcc));
+ if (document) {
+ IAccessible* msaaAccessible = nullptr;
+ document->GetNativeInterface((void**)&msaaAccessible); // does an addref
+ if (msaaAccessible) {
+ LRESULT result = ::LresultFromObject(IID_IAccessible, wParam,
+ msaaAccessible); // does an addref
+ msaaAccessible->Release(); // release extra addref
+ return result;
+ }
+ }
+ }
+ return 0;
+ }
+ case WM_NCHITTEST:
+ {
+ LRESULT lRet = ::DefWindowProc(hWnd, msg, wParam, lParam);
+ if (HTCLIENT == lRet)
+ lRet = HTTRANSPARENT;
+ return lRet;
+ }
+ }
+
+ return ::DefWindowProcW(hWnd, msg, wParam, lParam);
+}
diff --git a/accessible/windows/msaa/nsWinUtils.h b/accessible/windows/msaa/nsWinUtils.h
new file mode 100644
index 000000000..55089e722
--- /dev/null
+++ b/accessible/windows/msaa/nsWinUtils.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 nsWinUtils_h_
+#define nsWinUtils_h_
+
+#include <windows.h>
+
+#include "nsIDOMCSSStyleDeclaration.h"
+#include "nsCOMPtr.h"
+
+class nsIContent;
+
+namespace mozilla {
+namespace a11y {
+
+class DocAccessible;
+
+const LPCWSTR kClassNameRoot = L"MozillaUIWindowClass";
+const LPCWSTR kClassNameTabContent = L"MozillaContentWindowClass";
+const LPCWSTR kPropNameDocAcc = L"MozDocAccessible";
+
+class nsWinUtils
+{
+public:
+ /**
+ * Return computed styles declaration for the given node.
+ *
+ * @note Please use it carefully since it can shutdown the accessible tree
+ * you operate on.
+ */
+ static already_AddRefed<nsIDOMCSSStyleDeclaration>
+ GetComputedStyleDeclaration(nsIContent* aContent);
+
+ /**
+ * Start window emulation if presence of specific AT is detected.
+ */
+ static bool MaybeStartWindowEmulation();
+
+ /**
+ * Free resources used for window emulation.
+ */
+ static void ShutdownWindowEmulation();
+
+ /**
+ * Return true if window emulation is started.
+ */
+ static bool IsWindowEmulationStarted() { return sWindowEmulationStarted; }
+
+ /**
+ * Helper to register window class.
+ */
+ static void RegisterNativeWindow(LPCWSTR aWindowClass);
+
+ /**
+ * Helper to create a window.
+ */
+ static HWND CreateNativeWindow(LPCWSTR aWindowClass, HWND aParentWnd,
+ int aX, int aY, int aWidth, int aHeight,
+ bool aIsActive);
+
+ /**
+ * Helper to show window.
+ */
+ static void ShowNativeWindow(HWND aWnd);
+
+ /**
+ * Helper to hide window.
+ */
+ static void HideNativeWindow(HWND aWnd);
+
+private:
+ /**
+ * Flag that indicates if window emulation is started.
+ */
+ static bool sWindowEmulationStarted;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/sdn/moz.build b/accessible/windows/sdn/moz.build
new file mode 100644
index 000000000..ae0d5e20d
--- /dev/null
+++ b/accessible/windows/sdn/moz.build
@@ -0,0 +1,24 @@
+# -*- 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/.
+
+UNIFIED_SOURCES += [
+ 'sdnAccessible.cpp',
+ 'sdnDocAccessible.cpp',
+ 'sdnTextAccessible.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/accessible/base',
+ '/accessible/generic',
+ '/accessible/html',
+ '/accessible/windows/msaa',
+ '/accessible/xpcom',
+ '/accessible/xul',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
diff --git a/accessible/windows/sdn/sdnAccessible-inl.h b/accessible/windows/sdn/sdnAccessible-inl.h
new file mode 100644
index 000000000..a3d1a95e8
--- /dev/null
+++ b/accessible/windows/sdn/sdnAccessible-inl.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_sdnAccessible_inl_h_
+#define mozilla_a11y_sdnAccessible_inl_h_
+
+#include "sdnAccessible.h"
+
+#include "DocAccessible.h"
+#include "nsAccessibilityService.h"
+
+namespace mozilla {
+namespace a11y {
+
+inline DocAccessible*
+sdnAccessible::GetDocument() const
+{
+ return GetExistingDocAccessible(mNode->OwnerDoc());
+}
+
+inline Accessible*
+sdnAccessible::GetAccessible() const
+{
+ DocAccessible* document = GetDocument();
+ return document ? document->GetAccessibleEvenIfNotInMap(mNode) : nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_sdnAccessible_inl_h_
diff --git a/accessible/windows/sdn/sdnAccessible.cpp b/accessible/windows/sdn/sdnAccessible.cpp
new file mode 100644
index 000000000..909b0779c
--- /dev/null
+++ b/accessible/windows/sdn/sdnAccessible.cpp
@@ -0,0 +1,539 @@
+/* -*- 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 "sdnAccessible-inl.h"
+#include "ISimpleDOMNode_i.c"
+
+#include "DocAccessibleWrap.h"
+
+#include "nsAttrName.h"
+#include "nsCoreUtils.h"
+#include "nsIAccessibleTypes.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsIDOMCSSStyleDeclaration.h"
+#include "nsNameSpaceManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsWinUtils.h"
+#include "nsRange.h"
+
+#include "mozilla/dom/BorrowedAttrInfo.h"
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+STDMETHODIMP
+sdnAccessible::QueryInterface(REFIID aREFIID, void** aInstancePtr)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aInstancePtr)
+ return E_FAIL;
+ *aInstancePtr = nullptr;
+
+ if (aREFIID == IID_ISimpleDOMNode) {
+ *aInstancePtr = static_cast<ISimpleDOMNode*>(this);
+ AddRef();
+ return S_OK;
+ }
+
+ AccessibleWrap* accessible = static_cast<AccessibleWrap*>(GetAccessible());
+ if (accessible)
+ return accessible->QueryInterface(aREFIID, aInstancePtr);
+
+ // IUnknown* is the canonical one if and only if this accessible doesn't have
+ // an accessible.
+ if (aREFIID == IID_IUnknown) {
+ *aInstancePtr = static_cast<ISimpleDOMNode*>(this);
+ AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+sdnAccessible::get_nodeInfo(BSTR __RPC_FAR* aNodeName,
+ short __RPC_FAR* aNameSpaceID,
+ BSTR __RPC_FAR* aNodeValue,
+ unsigned int __RPC_FAR* aNumChildren,
+ unsigned int __RPC_FAR* aUniqueID,
+ unsigned short __RPC_FAR* aNodeType)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aNodeName || !aNameSpaceID || !aNodeValue || !aNumChildren ||
+ !aUniqueID || !aNodeType)
+ return E_INVALIDARG;
+
+ *aNodeName = nullptr;
+ *aNameSpaceID = 0;
+ *aNodeValue = nullptr;
+ *aNumChildren = 0;
+ *aUniqueID = 0;
+ *aNodeType = 0;
+
+ if (IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsCOMPtr<nsIDOMNode> DOMNode(do_QueryInterface(mNode));
+
+ uint16_t nodeType = 0;
+ DOMNode->GetNodeType(&nodeType);
+ *aNodeType = static_cast<unsigned short>(nodeType);
+
+ if (*aNodeType != NODETYPE_TEXT) {
+ nsAutoString nodeName;
+ DOMNode->GetNodeName(nodeName);
+ *aNodeName = ::SysAllocString(nodeName.get());
+ }
+
+ nsAutoString nodeValue;
+ DOMNode->GetNodeValue(nodeValue);
+ *aNodeValue = ::SysAllocString(nodeValue.get());
+
+ *aNameSpaceID = mNode->IsNodeOfType(nsINode::eCONTENT) ?
+ static_cast<short>(mNode->AsContent()->GetNameSpaceID()) : 0;
+
+ // This is a unique ID for every content node. The 3rd party accessibility
+ // application can compare this to the childID we return for events such as
+ // focus events, to correlate back to data nodes in their internal object
+ // model.
+ Accessible* accessible = GetAccessible();
+ if (accessible) {
+ *aUniqueID = AccessibleWrap::GetChildIDFor(accessible);
+ } else {
+ *aUniqueID = - NS_PTR_TO_INT32(static_cast<void*>(this));
+ }
+
+ *aNumChildren = mNode->GetChildCount();
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+sdnAccessible::get_attributes(unsigned short aMaxAttribs,
+ BSTR __RPC_FAR* aAttribNames,
+ short __RPC_FAR* aNameSpaceIDs,
+ BSTR __RPC_FAR* aAttribValues,
+ unsigned short __RPC_FAR* aNumAttribs)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aAttribNames || !aNameSpaceIDs || !aAttribValues || !aNumAttribs)
+ return E_INVALIDARG;
+
+ *aNumAttribs = 0;
+
+ if (IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ if (!mNode->IsElement())
+ return S_FALSE;
+
+ dom::Element* elm = mNode->AsElement();
+ uint32_t numAttribs = elm->GetAttrCount();
+ if (numAttribs > aMaxAttribs)
+ numAttribs = aMaxAttribs;
+
+ *aNumAttribs = static_cast<unsigned short>(numAttribs);
+
+ for (uint32_t index = 0; index < numAttribs; index++) {
+ aNameSpaceIDs[index] = 0;
+ aAttribValues[index] = aAttribNames[index] = nullptr;
+ nsAutoString attributeValue;
+
+ dom::BorrowedAttrInfo attr = elm->GetAttrInfoAt(index);
+ attr.mValue->ToString(attributeValue);
+
+ aNameSpaceIDs[index] = static_cast<short>(attr.mName->NamespaceID());
+ aAttribNames[index] = ::SysAllocString(attr.mName->LocalName()->GetUTF16String());
+ aAttribValues[index] = ::SysAllocString(attributeValue.get());
+ }
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+sdnAccessible::get_attributesForNames(unsigned short aMaxAttribs,
+ BSTR __RPC_FAR* aAttribNames,
+ short __RPC_FAR* aNameSpaceID,
+ BSTR __RPC_FAR* aAttribValues)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aAttribNames || !aNameSpaceID || !aAttribValues)
+ return E_INVALIDARG;
+
+ if (IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ if (!mNode->IsElement())
+ return S_FALSE;
+
+ nsCOMPtr<nsIDOMElement> domElement(do_QueryInterface(mNode));
+ nsNameSpaceManager* nameSpaceManager = nsNameSpaceManager::GetInstance();
+
+ int32_t index = 0;
+ for (index = 0; index < aMaxAttribs; index++) {
+ aAttribValues[index] = nullptr;
+ if (aAttribNames[index]) {
+ nsAutoString attributeValue, nameSpaceURI;
+ nsAutoString attributeName(nsDependentString(
+ static_cast<const wchar_t*>(aAttribNames[index])));
+
+ nsresult rv = NS_OK;
+ if (aNameSpaceID[index]>0 &&
+ NS_SUCCEEDED(nameSpaceManager->GetNameSpaceURI(aNameSpaceID[index],
+ nameSpaceURI))) {
+ rv = domElement->GetAttributeNS(nameSpaceURI, attributeName,
+ attributeValue);
+ } else {
+ rv = domElement->GetAttribute(attributeName, attributeValue);
+ }
+
+ if (NS_SUCCEEDED(rv))
+ aAttribValues[index] = ::SysAllocString(attributeValue.get());
+ }
+ }
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+sdnAccessible::get_computedStyle(unsigned short aMaxStyleProperties,
+ boolean aUseAlternateView,
+ BSTR __RPC_FAR* aStyleProperties,
+ BSTR __RPC_FAR* aStyleValues,
+ unsigned short __RPC_FAR* aNumStyleProperties)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aStyleProperties || aStyleValues || !aNumStyleProperties)
+ return E_INVALIDARG;
+
+ if (IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ *aNumStyleProperties = 0;
+
+ if (mNode->IsNodeOfType(nsINode::eDOCUMENT))
+ return S_FALSE;
+
+ nsCOMPtr<nsIDOMCSSStyleDeclaration> cssDecl =
+ nsWinUtils::GetComputedStyleDeclaration(mNode->AsContent());
+ NS_ENSURE_TRUE(cssDecl, E_FAIL);
+
+ uint32_t length = 0;
+ cssDecl->GetLength(&length);
+
+ uint32_t index = 0, realIndex = 0;
+ for (index = realIndex = 0; index < length && realIndex < aMaxStyleProperties;
+ index ++) {
+ nsAutoString property, value;
+
+ // Ignore -moz-* properties.
+ if (NS_SUCCEEDED(cssDecl->Item(index, property)) && property.CharAt(0) != '-')
+ cssDecl->GetPropertyValue(property, value); // Get property value
+
+ if (!value.IsEmpty()) {
+ aStyleProperties[realIndex] = ::SysAllocString(property.get());
+ aStyleValues[realIndex] = ::SysAllocString(value.get());
+ ++realIndex;
+ }
+ }
+
+ *aNumStyleProperties = static_cast<unsigned short>(realIndex);
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+sdnAccessible::get_computedStyleForProperties(unsigned short aNumStyleProperties,
+ boolean aUseAlternateView,
+ BSTR __RPC_FAR* aStyleProperties,
+ BSTR __RPC_FAR* aStyleValues)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aStyleProperties || !aStyleValues)
+ return E_INVALIDARG;
+
+ if (IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ if (mNode->IsNodeOfType(nsINode::eDOCUMENT))
+ return S_FALSE;
+
+ nsCOMPtr<nsIDOMCSSStyleDeclaration> cssDecl =
+ nsWinUtils::GetComputedStyleDeclaration(mNode->AsContent());
+ NS_ENSURE_TRUE(cssDecl, E_FAIL);
+
+ uint32_t index = 0;
+ for (index = 0; index < aNumStyleProperties; index++) {
+ nsAutoString value;
+ if (aStyleProperties[index])
+ cssDecl->GetPropertyValue(nsDependentString(aStyleProperties[index]), value); // Get property value
+ aStyleValues[index] = ::SysAllocString(value.get());
+ }
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+sdnAccessible::scrollTo(boolean aScrollTopLeft)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ DocAccessible* document = GetDocument();
+ if (!document) // that's IsDefunct check
+ return CO_E_OBJNOTCONNECTED;
+
+ if (!mNode->IsContent())
+ return S_FALSE;
+
+ uint32_t scrollType =
+ aScrollTopLeft ? nsIAccessibleScrollType::SCROLL_TYPE_TOP_LEFT :
+ nsIAccessibleScrollType::SCROLL_TYPE_BOTTOM_RIGHT;
+
+ nsCoreUtils::ScrollTo(document->PresShell(), mNode->AsContent(), scrollType);
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+sdnAccessible::get_parentNode(ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aNode)
+ return E_INVALIDARG;
+ *aNode = nullptr;
+
+ if (IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsINode* resultNode = mNode->GetParentNode();
+ if (resultNode) {
+ *aNode = static_cast<ISimpleDOMNode*>(new sdnAccessible(resultNode));
+ (*aNode)->AddRef();
+ }
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+sdnAccessible::get_firstChild(ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aNode)
+ return E_INVALIDARG;
+ *aNode = nullptr;
+
+ if (IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsINode* resultNode = mNode->GetFirstChild();
+ if (resultNode) {
+ *aNode = static_cast<ISimpleDOMNode*>(new sdnAccessible(resultNode));
+ (*aNode)->AddRef();
+ }
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+sdnAccessible::get_lastChild(ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aNode)
+ return E_INVALIDARG;
+ *aNode = nullptr;
+
+ if (IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsINode* resultNode = mNode->GetLastChild();
+ if (resultNode) {
+ *aNode = static_cast<ISimpleDOMNode*>(new sdnAccessible(resultNode));
+ (*aNode)->AddRef();
+ }
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+sdnAccessible::get_previousSibling(ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aNode)
+ return E_INVALIDARG;
+ *aNode = nullptr;
+
+ if (IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsINode* resultNode = mNode->GetPreviousSibling();
+ if (resultNode) {
+ *aNode = static_cast<ISimpleDOMNode*>(new sdnAccessible(resultNode));
+ (*aNode)->AddRef();
+ }
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+sdnAccessible::get_nextSibling(ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aNode)
+ return E_INVALIDARG;
+ *aNode = nullptr;
+
+ if (IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsINode* resultNode = mNode->GetNextSibling();
+ if (resultNode) {
+ *aNode = static_cast<ISimpleDOMNode*>(new sdnAccessible(resultNode));
+ (*aNode)->AddRef();
+ }
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+sdnAccessible::get_childAt(unsigned aChildIndex,
+ ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aNode)
+ return E_INVALIDARG;
+ *aNode = nullptr;
+
+ if (IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsINode* resultNode = mNode->GetChildAt(aChildIndex);
+ if (resultNode) {
+ *aNode = static_cast<ISimpleDOMNode*>(new sdnAccessible(resultNode));
+ (*aNode)->AddRef();
+ }
+
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+sdnAccessible::get_innerHTML(BSTR __RPC_FAR* aInnerHTML)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aInnerHTML)
+ return E_INVALIDARG;
+ *aInnerHTML = nullptr;
+
+ if (IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ if (!mNode->IsElement())
+ return S_FALSE;
+
+ nsAutoString innerHTML;
+ mNode->AsElement()->GetInnerHTML(innerHTML);
+ if (innerHTML.IsEmpty())
+ return S_FALSE;
+
+ *aInnerHTML = ::SysAllocStringLen(innerHTML.get(), innerHTML.Length());
+ if (!*aInnerHTML)
+ return E_OUTOFMEMORY;
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+sdnAccessible::get_localInterface(void __RPC_FAR *__RPC_FAR* aLocalInterface)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aLocalInterface)
+ return E_INVALIDARG;
+ *aLocalInterface = nullptr;
+
+ if (IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ *aLocalInterface = this;
+ AddRef();
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+sdnAccessible::get_language(BSTR __RPC_FAR* aLanguage)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aLanguage)
+ return E_INVALIDARG;
+ *aLanguage = nullptr;
+
+ if (IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsAutoString language;
+ if (mNode->IsContent())
+ nsCoreUtils::GetLanguageFor(mNode->AsContent(), nullptr, language);
+ if (language.IsEmpty()) { // Nothing found, so use document's language
+ mNode->OwnerDoc()->GetHeaderData(nsGkAtoms::headerContentLanguage,
+ language);
+ }
+
+ if (language.IsEmpty())
+ return S_FALSE;
+
+ *aLanguage = ::SysAllocStringLen(language.get(), language.Length());
+ if (!*aLanguage)
+ return E_OUTOFMEMORY;
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
diff --git a/accessible/windows/sdn/sdnAccessible.h b/accessible/windows/sdn/sdnAccessible.h
new file mode 100644
index 000000000..2876ad270
--- /dev/null
+++ b/accessible/windows/sdn/sdnAccessible.h
@@ -0,0 +1,119 @@
+/* -*- 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_sdnAccessible_h_
+#define mozilla_a11y_sdnAccessible_h_
+
+#include "ISimpleDOMNode.h"
+#include "AccessibleWrap.h"
+#include "IUnknownImpl.h"
+
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace a11y {
+
+class sdnAccessible final : public ISimpleDOMNode
+{
+public:
+ sdnAccessible(nsINode* aNode) :
+ mNode(aNode)
+ {
+ if (!mNode)
+ MOZ_CRASH();
+ }
+ ~sdnAccessible() { }
+
+ /**
+ * Retrun if the object is defunct.
+ */
+ bool IsDefunct() const { return !GetDocument(); }
+
+ /**
+ * Return a document accessible it belongs to if any.
+ */
+ DocAccessible* GetDocument() const;
+
+ /*
+ * Return associated accessible if any.
+ */
+ Accessible* GetAccessible() const;
+
+ //IUnknown
+ DECL_IUNKNOWN
+
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_nodeInfo(
+ /* [out] */ BSTR __RPC_FAR* aNodeName,
+ /* [out] */ short __RPC_FAR* aNameSpaceID,
+ /* [out] */ BSTR __RPC_FAR* aNodeValue,
+ /* [out] */ unsigned int __RPC_FAR* aNumChildren,
+ /* [out] */ unsigned int __RPC_FAR* aUniqueID,
+ /* [out][retval] */ unsigned short __RPC_FAR* aNodeType);
+
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_attributes(
+ /* [in] */ unsigned short aMaxAttribs,
+ /* [length_is][size_is][out] */ BSTR __RPC_FAR* aAttribNames,
+ /* [length_is][size_is][out] */ short __RPC_FAR* aNameSpaceIDs,
+ /* [length_is][size_is][out] */ BSTR __RPC_FAR* aAttribValues,
+ /* [out][retval] */ unsigned short __RPC_FAR* aNumAttribs);
+
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_attributesForNames(
+ /* [in] */ unsigned short aMaxAttribs,
+ /* [length_is][size_is][in] */ BSTR __RPC_FAR* aAttribNames,
+ /* [length_is][size_is][in] */ short __RPC_FAR* aNameSpaceID,
+ /* [length_is][size_is][retval] */ BSTR __RPC_FAR* aAttribValues);
+
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_computedStyle(
+ /* [in] */ unsigned short aMaxStyleProperties,
+ /* [in] */ boolean aUseAlternateView,
+ /* [length_is][size_is][out] */ BSTR __RPC_FAR* aStyleProperties,
+ /* [length_is][size_is][out] */ BSTR __RPC_FAR* aStyleValues,
+ /* [out][retval] */ unsigned short __RPC_FAR* aNumStyleProperties);
+
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_computedStyleForProperties(
+ /* [in] */ unsigned short aNumStyleProperties,
+ /* [in] */ boolean aUseAlternateView,
+ /* [length_is][size_is][in] */ BSTR __RPC_FAR* aStyleProperties,
+ /* [length_is][size_is][out][retval] */ BSTR __RPC_FAR* aStyleValues);
+
+ virtual HRESULT STDMETHODCALLTYPE scrollTo(/* [in] */ boolean aScrollTopLeft);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_parentNode(
+ /* [out][retval] */ ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_firstChild(
+ /* [out][retval] */ ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_lastChild(
+ /* [out][retval] */ ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_previousSibling(
+ /* [out][retval] */ ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nextSibling(
+ /* [out][retval] */ ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_childAt(
+ /* [in] */ unsigned aChildIndex,
+ /* [out][retval] */ ISimpleDOMNode __RPC_FAR *__RPC_FAR* aNode);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_innerHTML(
+ /* [out][retval] */ BSTR __RPC_FAR* aInnerHTML);
+
+ virtual /* [local][propget] */ HRESULT STDMETHODCALLTYPE get_localInterface(
+ /* [retval][out] */ void __RPC_FAR *__RPC_FAR* aLocalInterface);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_language(
+ /* [out][retval] */ BSTR __RPC_FAR* aLanguage);
+
+private:
+ nsCOMPtr<nsINode> mNode;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_sdnAccessible_h_
diff --git a/accessible/windows/sdn/sdnDocAccessible.cpp b/accessible/windows/sdn/sdnDocAccessible.cpp
new file mode 100644
index 000000000..07b39e66f
--- /dev/null
+++ b/accessible/windows/sdn/sdnDocAccessible.cpp
@@ -0,0 +1,157 @@
+/* -*- 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 "sdnDocAccessible.h"
+
+#include "ISimpleDOMDocument_i.c"
+
+#include "nsNameSpaceManager.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// sdnDocAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+IMPL_IUNKNOWN_QUERY_HEAD(sdnDocAccessible)
+ IMPL_IUNKNOWN_QUERY_IFACE(ISimpleDOMDocument)
+IMPL_IUNKNOWN_QUERY_TAIL_AGGREGATED(mAccessible)
+
+STDMETHODIMP
+sdnDocAccessible::get_URL(BSTR __RPC_FAR* aURL)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aURL)
+ return E_INVALIDARG;
+ *aURL = nullptr;
+
+ if (mAccessible->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsAutoString URL;
+ mAccessible->URL(URL);
+ if (URL.IsEmpty())
+ return S_FALSE;
+
+ *aURL = ::SysAllocStringLen(URL.get(), URL.Length());
+ return *aURL ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+sdnDocAccessible::get_title(BSTR __RPC_FAR* aTitle)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aTitle)
+ return E_INVALIDARG;
+ *aTitle = nullptr;
+
+ if (mAccessible->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsAutoString title;
+ mAccessible->Title(title);
+ *aTitle = ::SysAllocStringLen(title.get(), title.Length());
+ return *aTitle ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+sdnDocAccessible::get_mimeType(BSTR __RPC_FAR* aMimeType)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aMimeType)
+ return E_INVALIDARG;
+ *aMimeType = nullptr;
+
+ if (mAccessible->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsAutoString mimeType;
+ mAccessible->MimeType(mimeType);
+ if (mimeType.IsEmpty())
+ return S_FALSE;
+
+ *aMimeType = ::SysAllocStringLen(mimeType.get(), mimeType.Length());
+ return *aMimeType ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+sdnDocAccessible::get_docType(BSTR __RPC_FAR* aDocType)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aDocType)
+ return E_INVALIDARG;
+ *aDocType = nullptr;
+
+ if (mAccessible->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsAutoString docType;
+ mAccessible->DocType(docType);
+ if (docType.IsEmpty())
+ return S_FALSE;
+
+ *aDocType = ::SysAllocStringLen(docType.get(), docType.Length());
+ return *aDocType ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+sdnDocAccessible::get_nameSpaceURIForID(short aNameSpaceID,
+ BSTR __RPC_FAR* aNameSpaceURI)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aNameSpaceURI)
+ return E_INVALIDARG;
+ *aNameSpaceURI = nullptr;
+
+ if (mAccessible->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ if (aNameSpaceID < 0)
+ return E_INVALIDARG; // -1 is kNameSpaceID_Unknown
+
+ nsAutoString nameSpaceURI;
+ nsNameSpaceManager* nameSpaceManager = nsNameSpaceManager::GetInstance();
+ if (nameSpaceManager)
+ nameSpaceManager->GetNameSpaceURI(aNameSpaceID, nameSpaceURI);
+
+ if (nameSpaceURI.IsEmpty())
+ return S_FALSE;
+
+ *aNameSpaceURI = ::SysAllocStringLen(nameSpaceURI.get(),
+ nameSpaceURI.Length());
+
+ return *aNameSpaceURI ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+sdnDocAccessible::put_alternateViewMediaTypes(BSTR __RPC_FAR* aCommaSeparatedMediaTypes)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aCommaSeparatedMediaTypes)
+ return E_INVALIDARG;
+ *aCommaSeparatedMediaTypes = nullptr;
+
+ return mAccessible->IsDefunct() ? CO_E_OBJNOTCONNECTED : E_NOTIMPL;
+
+ A11Y_TRYBLOCK_END
+}
diff --git a/accessible/windows/sdn/sdnDocAccessible.h b/accessible/windows/sdn/sdnDocAccessible.h
new file mode 100644
index 000000000..22c7124b2
--- /dev/null
+++ b/accessible/windows/sdn/sdnDocAccessible.h
@@ -0,0 +1,53 @@
+/* -*- 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_sdnDocAccessible_h_
+#define mozilla_a11y_sdnDocAccessible_h_
+
+#include "ISimpleDOMDocument.h"
+#include "IUnknownImpl.h"
+
+#include "DocAccessibleWrap.h"
+
+namespace mozilla {
+namespace a11y {
+
+class sdnDocAccessible final : public ISimpleDOMDocument
+{
+public:
+ sdnDocAccessible(DocAccessibleWrap* aAccessible) : mAccessible(aAccessible) {};
+ ~sdnDocAccessible() { };
+
+ DECL_IUNKNOWN
+
+ // ISimpleDOMDocument
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_URL(
+ /* [out] */ BSTR __RPC_FAR *url);
+
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_title(
+ /* [out] */ BSTR __RPC_FAR *title);
+
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_mimeType(
+ /* [out] */ BSTR __RPC_FAR *mimeType);
+
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_docType(
+ /* [out] */ BSTR __RPC_FAR *docType);
+
+ virtual /* [id][propget] */ HRESULT STDMETHODCALLTYPE get_nameSpaceURIForID(
+ /* [in] */ short nameSpaceID,
+ /* [out] */ BSTR __RPC_FAR *nameSpaceURI);
+
+ virtual /* [id] */ HRESULT STDMETHODCALLTYPE put_alternateViewMediaTypes(
+ /* [in] */ BSTR __RPC_FAR *commaSeparatedMediaTypes);
+
+protected:
+ RefPtr<DocAccessibleWrap> mAccessible;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/sdn/sdnTextAccessible.cpp b/accessible/windows/sdn/sdnTextAccessible.cpp
new file mode 100644
index 000000000..b51caf44e
--- /dev/null
+++ b/accessible/windows/sdn/sdnTextAccessible.cpp
@@ -0,0 +1,210 @@
+/* -*- 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 "sdnTextAccessible.h"
+
+#include "ISimpleDOMText_i.c"
+
+#include "nsCoreUtils.h"
+#include "DocAccessible.h"
+
+#include "nsIFrame.h"
+#include "nsFontMetrics.h"
+#include "nsPresContext.h"
+#include "nsLayoutUtils.h"
+#include "nsRange.h"
+#include "gfxFont.h"
+#include "nsIAccessibleTypes.h"
+#include "mozilla/gfx/2D.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// sdnTextAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+IMPL_IUNKNOWN_QUERY_HEAD(sdnTextAccessible)
+ IMPL_IUNKNOWN_QUERY_IFACE(ISimpleDOMText)
+IMPL_IUNKNOWN_QUERY_TAIL_AGGREGATED(mAccessible)
+
+STDMETHODIMP
+sdnTextAccessible::get_domText(BSTR __RPC_FAR* aText)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aText)
+ return E_INVALIDARG;
+ *aText = nullptr;
+
+ if (mAccessible->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsAutoString nodeValue;
+
+ nsCOMPtr<nsIDOMNode> DOMNode(do_QueryInterface(mAccessible->GetContent()));
+ DOMNode->GetNodeValue(nodeValue);
+ if (nodeValue.IsEmpty())
+ return S_FALSE;
+
+ *aText = ::SysAllocStringLen(nodeValue.get(), nodeValue.Length());
+ return *aText ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+sdnTextAccessible::get_clippedSubstringBounds(unsigned int aStartIndex,
+ unsigned int aEndIndex,
+ int __RPC_FAR* aX,
+ int __RPC_FAR* aY,
+ int __RPC_FAR* aWidth,
+ int __RPC_FAR* aHeight)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ nscoord x = 0, y = 0, width = 0, height = 0;
+ HRESULT rv = get_unclippedSubstringBounds(aStartIndex, aEndIndex,
+ &x, &y, &width, &height);
+ if (FAILED(rv))
+ return rv;
+
+ DocAccessible* document = mAccessible->Document();
+ NS_ASSERTION(document,
+ "There must always be a doc accessible, but there isn't. Crash!");
+
+ nsIntRect docRect = document->Bounds();
+ nsIntRect unclippedRect(x, y, width, height);
+
+ nsIntRect clippedRect;
+ clippedRect.IntersectRect(unclippedRect, docRect);
+
+ *aX = clippedRect.x;
+ *aY = clippedRect.y;
+ *aWidth = clippedRect.width;
+ *aHeight = clippedRect.height;
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+sdnTextAccessible::get_unclippedSubstringBounds(unsigned int aStartIndex,
+ unsigned int aEndIndex,
+ int __RPC_FAR* aX,
+ int __RPC_FAR* aY,
+ int __RPC_FAR* aWidth,
+ int __RPC_FAR* aHeight)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aX || !aY || !aWidth || !aHeight)
+ return E_INVALIDARG;
+ *aX = *aY = *aWidth = *aHeight = 0;
+
+ if (mAccessible->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsIFrame *frame = mAccessible->GetFrame();
+ NS_ENSURE_TRUE(frame, E_FAIL);
+
+ nsPoint startPoint, endPoint;
+ nsIFrame* startFrame = GetPointFromOffset(frame, aStartIndex, true, startPoint);
+ nsIFrame* endFrame = GetPointFromOffset(frame, aEndIndex, false, endPoint);
+ if (!startFrame || !endFrame)
+ return E_FAIL;
+
+ nsRect sum;
+ nsIFrame* iter = startFrame;
+ nsIFrame* stopLoopFrame = endFrame->GetNextContinuation();
+ for (; iter != stopLoopFrame; iter = iter->GetNextContinuation()) {
+ nsRect rect = iter->GetScreenRectInAppUnits();
+ nscoord start = (iter == startFrame) ? startPoint.x : 0;
+ nscoord end = (iter == endFrame) ? endPoint.x : rect.width;
+ rect.x += start;
+ rect.width = end - start;
+ sum.UnionRect(sum, rect);
+ }
+
+ nsPresContext* presContext = mAccessible->Document()->PresContext();
+ *aX = presContext->AppUnitsToDevPixels(sum.x);
+ *aY = presContext->AppUnitsToDevPixels(sum.y);
+ *aWidth = presContext->AppUnitsToDevPixels(sum.width);
+ *aHeight = presContext->AppUnitsToDevPixels(sum.height);
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+sdnTextAccessible::scrollToSubstring(unsigned int aStartIndex,
+ unsigned int aEndIndex)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (mAccessible->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ RefPtr<nsRange> range = new nsRange(mAccessible->GetContent());
+ if (NS_FAILED(range->SetStart(mAccessible->GetContent(), aStartIndex)))
+ return E_FAIL;
+
+ if (NS_FAILED(range->SetEnd(mAccessible->GetContent(), aEndIndex)))
+ return E_FAIL;
+
+ nsresult rv =
+ nsCoreUtils::ScrollSubstringTo(mAccessible->GetFrame(), range,
+ nsIAccessibleScrollType::SCROLL_TYPE_ANYWHERE);
+ return GetHRESULT(rv);
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+sdnTextAccessible::get_fontFamily(BSTR __RPC_FAR* aFontFamily)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aFontFamily)
+ return E_INVALIDARG;
+ *aFontFamily = nullptr;
+
+ if (mAccessible->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ nsIFrame* frame = mAccessible->GetFrame();
+ if (!frame)
+ return E_FAIL;
+
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(frame, 1.0f);
+
+ const nsString& name =
+ fm->GetThebesFontGroup()->GetFirstValidFont()->GetName();
+ if (name.IsEmpty())
+ return S_FALSE;
+
+ *aFontFamily = ::SysAllocStringLen(name.get(), name.Length());
+ return *aFontFamily ? S_OK : E_OUTOFMEMORY;
+
+ A11Y_TRYBLOCK_END
+}
+
+nsIFrame*
+sdnTextAccessible::GetPointFromOffset(nsIFrame* aContainingFrame,
+ int32_t aOffset,
+ bool aPreferNext,
+ nsPoint& aOutPoint)
+{
+ nsIFrame* textFrame = nullptr;
+ int32_t outOffset;
+ aContainingFrame->GetChildFrameContainingOffset(aOffset, aPreferNext,
+ &outOffset, &textFrame);
+ if (textFrame)
+ textFrame->GetPointFromOffset(aOffset, &aOutPoint);
+
+ return textFrame;
+}
diff --git a/accessible/windows/sdn/sdnTextAccessible.h b/accessible/windows/sdn/sdnTextAccessible.h
new file mode 100644
index 000000000..ed8eecf29
--- /dev/null
+++ b/accessible/windows/sdn/sdnTextAccessible.h
@@ -0,0 +1,71 @@
+/* -*- 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_sdnTextAccessible_h_
+#define mozilla_a11y_sdnTextAccessible_h_
+
+#include "ISimpleDOMText.h"
+#include "IUnknownImpl.h"
+
+#include "AccessibleWrap.h"
+
+class nsIFrame;
+struct nsPoint;
+
+namespace mozilla {
+namespace a11y {
+
+class sdnTextAccessible final : public ISimpleDOMText
+{
+public:
+ sdnTextAccessible(AccessibleWrap* aAccessible) : mAccessible(aAccessible) {};
+ ~sdnTextAccessible() {}
+
+ DECL_IUNKNOWN
+
+ // ISimpleDOMText
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_domText(
+ /* [retval][out] */ BSTR __RPC_FAR *aText);
+
+ virtual HRESULT STDMETHODCALLTYPE get_clippedSubstringBounds(
+ /* [in] */ unsigned int startIndex,
+ /* [in] */ unsigned int endIndex,
+ /* [out] */ int __RPC_FAR* aX,
+ /* [out] */ int __RPC_FAR* aY,
+ /* [out] */ int __RPC_FAR* aWidth,
+ /* [out] */ int __RPC_FAR* aHeight);
+
+ virtual HRESULT STDMETHODCALLTYPE get_unclippedSubstringBounds(
+ /* [in] */ unsigned int aStartIndex,
+ /* [in] */ unsigned int aEndIndex,
+ /* [out] */ int __RPC_FAR* aX,
+ /* [out] */ int __RPC_FAR* aY,
+ /* [out] */ int __RPC_FAR* aWidth,
+ /* [out] */ int __RPC_FAR* aHeight);
+
+ virtual HRESULT STDMETHODCALLTYPE scrollToSubstring(
+ /* [in] */ unsigned int aStartIndex,
+ /* [in] */ unsigned int aEndIndex);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_fontFamily(
+ /* [retval][out] */ BSTR __RPC_FAR* aFontFamily);
+
+private:
+ /**
+ * Return child frame containing offset on success.
+ */
+ nsIFrame* GetPointFromOffset(nsIFrame* aContainingFrame,
+ int32_t aOffset, bool aPreferNext,
+ nsPoint& aOutPoint);
+
+ RefPtr<AccessibleWrap> mAccessible;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/windows/uia/moz.build b/accessible/windows/uia/moz.build
new file mode 100644
index 000000000..afc150e11
--- /dev/null
+++ b/accessible/windows/uia/moz.build
@@ -0,0 +1,22 @@
+# -*- 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/.
+
+SOURCES += [
+ 'uiaRawElmProvider.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/accessible/base',
+ '/accessible/generic',
+ '/accessible/html',
+ '/accessible/windows/msaa',
+ '/accessible/xpcom',
+ '/accessible/xul',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
diff --git a/accessible/windows/uia/uiaRawElmProvider.cpp b/accessible/windows/uia/uiaRawElmProvider.cpp
new file mode 100644
index 000000000..54e54766d
--- /dev/null
+++ b/accessible/windows/uia/uiaRawElmProvider.cpp
@@ -0,0 +1,246 @@
+/* -*- 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 "uiaRawElmProvider.h"
+
+#include "AccessibleWrap.h"
+#include "ARIAMap.h"
+#include "nsIPersistentProperties2.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// uiaRawElmProvider
+////////////////////////////////////////////////////////////////////////////////
+
+IMPL_IUNKNOWN2(uiaRawElmProvider,
+ IAccessibleEx,
+ IRawElementProviderSimple)
+
+////////////////////////////////////////////////////////////////////////////////
+// IAccessibleEx
+
+STDMETHODIMP
+uiaRawElmProvider::GetObjectForChild(long aIdChild,
+ __RPC__deref_out_opt IAccessibleEx** aAccEx)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aAccEx)
+ return E_INVALIDARG;
+
+ *aAccEx = nullptr;
+
+ return mAcc->IsDefunct() ? CO_E_OBJNOTCONNECTED : S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+uiaRawElmProvider::GetIAccessiblePair(__RPC__deref_out_opt IAccessible** aAcc,
+ __RPC__out long* aIdChild)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aAcc || !aIdChild)
+ return E_INVALIDARG;
+
+ *aAcc = nullptr;
+ *aIdChild = 0;
+
+ if (mAcc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ *aIdChild = CHILDID_SELF;
+ *aAcc = mAcc;
+ mAcc->AddRef();
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+uiaRawElmProvider::GetRuntimeId(__RPC__deref_out_opt SAFEARRAY** aRuntimeIds)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aRuntimeIds)
+ return E_INVALIDARG;
+
+ int ids[] = { UiaAppendRuntimeId, static_cast<int>(reinterpret_cast<intptr_t>(mAcc->UniqueID())) };
+ *aRuntimeIds = SafeArrayCreateVector(VT_I4, 0, 2);
+ if (!*aRuntimeIds)
+ return E_OUTOFMEMORY;
+
+ for (LONG i = 0; i < (LONG)ArrayLength(ids); i++)
+ SafeArrayPutElement(*aRuntimeIds, &i, (void*)&(ids[i]));
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+uiaRawElmProvider::ConvertReturnedElement(__RPC__in_opt IRawElementProviderSimple* aRawElmProvider,
+ __RPC__deref_out_opt IAccessibleEx** aAccEx)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aRawElmProvider || !aAccEx)
+ return E_INVALIDARG;
+
+ *aAccEx = nullptr;
+
+ void* instancePtr = nullptr;
+ HRESULT hr = aRawElmProvider->QueryInterface(IID_IAccessibleEx, &instancePtr);
+ if (SUCCEEDED(hr))
+ *aAccEx = static_cast<IAccessibleEx*>(instancePtr);
+
+ return hr;
+
+ A11Y_TRYBLOCK_END
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// IRawElementProviderSimple
+
+STDMETHODIMP
+uiaRawElmProvider::get_ProviderOptions(__RPC__out enum ProviderOptions* aOptions)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aOptions)
+ return E_INVALIDARG;
+
+ // This method is not used with IAccessibleEx implementations.
+ *aOptions = ProviderOptions_ServerSideProvider;
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+uiaRawElmProvider::GetPatternProvider(PATTERNID aPatternId,
+ __RPC__deref_out_opt IUnknown** aPatternProvider)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aPatternProvider)
+ return E_INVALIDARG;
+
+ *aPatternProvider = nullptr;
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+uiaRawElmProvider::GetPropertyValue(PROPERTYID aPropertyId,
+ __RPC__out VARIANT* aPropertyValue)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aPropertyValue)
+ return E_INVALIDARG;
+
+ if (mAcc->IsDefunct())
+ return CO_E_OBJNOTCONNECTED;
+
+ aPropertyValue->vt = VT_EMPTY;
+
+ switch (aPropertyId) {
+ // Accelerator Key / shortcut.
+ case UIA_AcceleratorKeyPropertyId: {
+ nsAutoString keyString;
+
+ mAcc->KeyboardShortcut().ToString(keyString);
+
+ if (!keyString.IsEmpty()) {
+ aPropertyValue->vt = VT_BSTR;
+ aPropertyValue->bstrVal = ::SysAllocString(keyString.get());
+ return S_OK;
+ }
+
+ break;
+ }
+
+ // Access Key / mneumonic.
+ case UIA_AccessKeyPropertyId: {
+ nsAutoString keyString;
+
+ mAcc->AccessKey().ToString(keyString);
+
+ if (!keyString.IsEmpty()) {
+ aPropertyValue->vt = VT_BSTR;
+ aPropertyValue->bstrVal = ::SysAllocString(keyString.get());
+ return S_OK;
+ }
+
+ break;
+ }
+
+ //ARIA Role / shortcut
+ case UIA_AriaRolePropertyId: {
+ nsAutoString xmlRoles;
+
+ nsCOMPtr<nsIPersistentProperties> attributes = mAcc->Attributes();
+ attributes->GetStringProperty(NS_LITERAL_CSTRING("xml-roles"), xmlRoles);
+
+ if(!xmlRoles.IsEmpty()) {
+ aPropertyValue->vt = VT_BSTR;
+ aPropertyValue->bstrVal = ::SysAllocString(xmlRoles.get());
+ return S_OK;
+ }
+
+ break;
+ }
+
+ //ARIA Properties
+ case UIA_AriaPropertiesPropertyId: {
+ nsAutoString ariaProperties;
+
+ aria::AttrIterator attribIter(mAcc->GetContent());
+ nsAutoString attribName, attribValue;
+ while (attribIter.Next(attribName, attribValue)) {
+ ariaProperties.Append(attribName);
+ ariaProperties.Append('=');
+ ariaProperties.Append(attribValue);
+ ariaProperties.Append(';');
+ }
+
+ if (!ariaProperties.IsEmpty()) {
+ //remove last delimiter:
+ ariaProperties.Truncate(ariaProperties.Length()-1);
+ aPropertyValue->vt = VT_BSTR;
+ aPropertyValue->bstrVal = ::SysAllocString(ariaProperties.get());
+ return S_OK;
+ }
+
+ break;
+ }
+ }
+
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
+
+STDMETHODIMP
+uiaRawElmProvider::get_HostRawElementProvider(__RPC__deref_out_opt IRawElementProviderSimple** aRawElmProvider)
+{
+ A11Y_TRYBLOCK_BEGIN
+
+ if (!aRawElmProvider)
+ return E_INVALIDARG;
+
+ // This method is not used with IAccessibleEx implementations.
+ *aRawElmProvider = nullptr;
+ return S_OK;
+
+ A11Y_TRYBLOCK_END
+}
diff --git a/accessible/windows/uia/uiaRawElmProvider.h b/accessible/windows/uia/uiaRawElmProvider.h
new file mode 100644
index 000000000..f32daa19b
--- /dev/null
+++ b/accessible/windows/uia/uiaRawElmProvider.h
@@ -0,0 +1,75 @@
+/* -*- 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_uiaRawElmProvider_h__
+#define mozilla_a11y_uiaRawElmProvider_h__
+
+#include "objbase.h"
+#include "AccessibleWrap.h"
+#include "IUnknownImpl.h"
+#include "uiautomation.h"
+
+namespace mozilla {
+namespace a11y {
+
+class AccessibleWrap;
+
+/**
+ * IRawElementProviderSimple implementation (maintains IAccessibleEx approach).
+ */
+class uiaRawElmProvider final : public IAccessibleEx,
+ public IRawElementProviderSimple
+{
+public:
+ uiaRawElmProvider(AccessibleWrap* aAcc) : mAcc(aAcc) { }
+
+ // IUnknown
+ DECL_IUNKNOWN
+
+ // IAccessibleEx
+ virtual HRESULT STDMETHODCALLTYPE GetObjectForChild(
+ /* [in] */ long aIdChild,
+ /* [retval][out] */ __RPC__deref_out_opt IAccessibleEx** aAccEx);
+
+ virtual HRESULT STDMETHODCALLTYPE GetIAccessiblePair(
+ /* [out] */ __RPC__deref_out_opt IAccessible** aAcc,
+ /* [out] */ __RPC__out long* aIdChild);
+
+ virtual HRESULT STDMETHODCALLTYPE GetRuntimeId(
+ /* [retval][out] */ __RPC__deref_out_opt SAFEARRAY** aRuntimeIds);
+
+ virtual HRESULT STDMETHODCALLTYPE ConvertReturnedElement(
+ /* [in] */ __RPC__in_opt IRawElementProviderSimple* aRawElmProvider,
+ /* [out] */ __RPC__deref_out_opt IAccessibleEx** aAccEx);
+
+ // IRawElementProviderSimple
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_ProviderOptions(
+ /* [retval][out] */ __RPC__out enum ProviderOptions* aProviderOptions);
+
+ virtual HRESULT STDMETHODCALLTYPE GetPatternProvider(
+ /* [in] */ PATTERNID aPatternId,
+ /* [retval][out] */ __RPC__deref_out_opt IUnknown** aPatternProvider);
+
+ virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
+ /* [in] */ PROPERTYID aPropertyId,
+ /* [retval][out] */ __RPC__out VARIANT* aPropertyValue);
+
+ virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_HostRawElementProvider(
+ /* [retval][out] */ __RPC__deref_out_opt IRawElementProviderSimple** aRawElmProvider);
+
+private:
+ uiaRawElmProvider() = delete;
+ uiaRawElmProvider& operator =(const uiaRawElmProvider&) = delete;
+ uiaRawElmProvider(const uiaRawElmProvider&) = delete;
+
+protected:
+ RefPtr<AccessibleWrap> mAcc;
+};
+
+} // a11y namespace
+} // mozilla namespace
+
+#endif
diff --git a/accessible/xpcom/AccEventGen.py b/accessible/xpcom/AccEventGen.py
new file mode 100755
index 000000000..6af54c34d
--- /dev/null
+++ b/accessible/xpcom/AccEventGen.py
@@ -0,0 +1,228 @@
+#!/usr/bin/env 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/.
+
+import sys
+import os
+
+import buildconfig
+import mozpack.path as mozpath
+
+# The xpidl parser is not incorporated in the in-tree virtualenv.
+xpidl_dir = mozpath.join(buildconfig.topsrcdir, 'xpcom', 'idl-parser',
+ 'xpidl')
+xpidl_cachedir = mozpath.join(buildconfig.topobjdir, 'xpcom', 'idl-parser',
+ 'xpidl')
+sys.path.extend([xpidl_dir, xpidl_cachedir])
+import xpidl
+
+# Instantiate the parser.
+p = xpidl.IDLParser()
+
+def findIDL(includePath, interfaceFileName):
+ for d in includePath:
+ path = mozpath.join(d, interfaceFileName)
+ if os.path.exists(path):
+ return path
+ raise BaseException("No IDL file found for interface %s "
+ "in include path %r"
+ % (interfaceFileName, includePath))
+
+def loadEventIDL(parser, includePath, eventname):
+ eventidl = ("nsIAccessible%s.idl" % eventname)
+ idlFile = findIDL(includePath, eventidl)
+ idl = p.parse(open(idlFile).read(), idlFile)
+ idl.resolve(includePath, p)
+ return idl, idlFile
+
+class Configuration:
+ def __init__(self, filename):
+ config = {}
+ execfile(filename, config)
+ self.simple_events = config.get('simple_events', [])
+
+def firstCap(str):
+ return str[0].upper() + str[1:]
+
+def writeAttributeParams(a):
+ return ("%s a%s" % (a.realtype.nativeType('in'), firstCap(a.name)))
+
+def print_header_file(fd, conf, incdirs):
+ idl_paths = set()
+
+ fd.write("/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n")
+ fd.write("#ifndef _mozilla_a11y_generated_AccEvents_h_\n"
+ "#define _mozilla_a11y_generated_AccEvents_h_\n\n")
+ fd.write("#include \"nscore.h\"\n")
+ fd.write("#include \"nsCOMPtr.h\"\n")
+ fd.write("#include \"nsCycleCollectionParticipant.h\"\n")
+ fd.write("#include \"nsString.h\"\n")
+ for e in conf.simple_events:
+ fd.write("#include \"nsIAccessible%s.h\"\n" % e)
+ for e in conf.simple_events:
+ idl, idl_path = loadEventIDL(p, incdirs, e)
+ idl_paths.add(idl_path)
+ for iface in filter(lambda p: p.kind == "interface", idl.productions):
+ classname = ("xpcAcc%s" % e)
+ baseinterfaces = interfaces(iface)
+
+ fd.write("\nclass %s final : public %s\n" % (classname, iface.name))
+ fd.write("{\n")
+ fd.write("public:\n")
+
+ attributes = allAttributes(iface)
+ args = map(writeAttributeParams, attributes)
+ fd.write(" %s(%s) :\n" % (classname, ", ".join(args)))
+
+ initializers = []
+ for a in attributes:
+ initializers.append("m%s(a%s)" % (firstCap(a.name), firstCap(a.name)))
+ fd.write(" %s\n {}\n\n" % ", ".join(initializers))
+ fd.write(" NS_DECL_CYCLE_COLLECTING_ISUPPORTS\n")
+ fd.write(" NS_DECL_CYCLE_COLLECTION_CLASS(%s)\n" % (classname))
+
+ for iface in filter(lambda i: i.name != "nsISupports", baseinterfaces):
+ fd.write(" NS_DECL_%s\n" % iface.name.upper())
+
+ fd.write("\nprivate:\n")
+ fd.write(" ~%s() {}\n\n" % classname)
+ for a in attributes:
+ fd.write(" %s\n" % attributeVariableTypeAndName(a))
+ fd.write("};\n\n")
+
+ fd.write("#endif\n")
+
+ return idl_paths
+
+def interfaceAttributeTypes(idl):
+ ifaces = filter(lambda p: p.kind == "interface", idl.productions)
+ attributes = []
+ for i in ifaces:
+ ifaceAttributes = allAttributes(i)
+ attributes.extend(ifaceAttributes)
+ ifaceAttrs = filter(lambda a: a.realtype.nativeType("in").endswith("*"), attributes)
+ return map(lambda a: a.realtype.nativeType("in").strip(" *"), ifaceAttrs)
+
+def print_cpp(idl, fd, conf, eventname):
+ for p in idl.productions:
+ if p.kind == 'interface':
+ write_cpp(eventname, p, fd)
+
+def print_cpp_file(fd, conf, incdirs):
+ idl_paths = set()
+ fd.write("/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n\n")
+ fd.write('#include "xpcAccEvents.h"\n')
+
+ includes = []
+ for e in conf.simple_events:
+ if not e in includes:
+ includes.append(("nsIAccessible%s" % e))
+
+ types = []
+ for e in conf.simple_events:
+ idl, idl_path = loadEventIDL(p, incdirs, e)
+ idl_paths.add(idl_path)
+ types.extend(interfaceAttributeTypes(idl))
+
+ for c in types:
+ fd.write("#include \"%s.h\"\n" % c)
+
+ fd.write("\n")
+ for e in conf.simple_events:
+ idl, idl_path = loadEventIDL(p, incdirs, e)
+ idl_paths.add(idl_path)
+ print_cpp(idl, fd, conf, e)
+
+ return idl_paths
+
+def attributeVariableTypeAndName(a):
+ if a.realtype.nativeType('in').endswith('*'):
+ l = ["nsCOMPtr<%s> m%s;" % (a.realtype.nativeType('in').strip('* '),
+ firstCap(a.name))]
+ elif a.realtype.nativeType('in').count("nsAString"):
+ l = ["nsString m%s;" % firstCap(a.name)]
+ elif a.realtype.nativeType('in').count("nsACString"):
+ l = ["nsCString m%s;" % firstCap(a.name)]
+ else:
+ l = ["%sm%s;" % (a.realtype.nativeType('in'),
+ firstCap(a.name))]
+ return ", ".join(l)
+
+def writeAttributeGetter(fd, classname, a):
+ fd.write("NS_IMETHODIMP\n")
+ fd.write("%s::Get%s(" % (classname, firstCap(a.name)))
+ if a.realtype.nativeType('in').endswith('*'):
+ fd.write("%s** a%s" % (a.realtype.nativeType('in').strip('* '), firstCap(a.name)))
+ elif a.realtype.nativeType('in').count("nsAString"):
+ fd.write("nsAString& a%s" % firstCap(a.name))
+ elif a.realtype.nativeType('in').count("nsACString"):
+ fd.write("nsACString& a%s" % firstCap(a.name))
+ else:
+ fd.write("%s*a%s" % (a.realtype.nativeType('in'), firstCap(a.name)))
+ fd.write(")\n");
+ fd.write("{\n");
+ if a.realtype.nativeType('in').endswith('*'):
+ fd.write(" NS_IF_ADDREF(*a%s = m%s);\n" % (firstCap(a.name), firstCap(a.name)))
+ elif a.realtype.nativeType('in').count("nsAString"):
+ fd.write(" a%s = m%s;\n" % (firstCap(a.name), firstCap(a.name)))
+ elif a.realtype.nativeType('in').count("nsACString"):
+ fd.write(" a%s = m%s;\n" % (firstCap(a.name), firstCap(a.name)))
+ else:
+ fd.write(" *a%s = m%s;\n" % (firstCap(a.name), firstCap(a.name)))
+ fd.write(" return NS_OK;\n");
+ fd.write("}\n\n");
+
+def interfaces(iface):
+ interfaces = []
+ while iface.base:
+ interfaces.append(iface)
+ iface = iface.idl.getName(iface.base, iface.location)
+ interfaces.append(iface)
+ interfaces.reverse()
+ return interfaces
+
+def allAttributes(iface):
+ attributes = []
+ for i in interfaces(iface):
+ attrs = filter(lambda m: isinstance(m, xpidl.Attribute), i.members)
+ attributes.extend(attrs)
+
+ return attributes
+
+def write_cpp(eventname, iface, fd):
+ classname = "xpcAcc%s" % eventname
+ attributes = allAttributes(iface)
+ ccattributes = filter(lambda m: m.realtype.nativeType('in').endswith('*'), attributes)
+ fd.write("NS_IMPL_CYCLE_COLLECTION(%s" % classname)
+ for c in ccattributes:
+ fd.write(", m%s" % firstCap(c.name))
+ fd.write(")\n\n");
+
+ fd.write("NS_IMPL_CYCLE_COLLECTING_ADDREF(%s)\n" % classname)
+ fd.write("NS_IMPL_CYCLE_COLLECTING_RELEASE(%s)\n\n" % classname)
+
+ fd.write("NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(%s)\n" % classname)
+ for baseiface in interfaces(iface):
+ fd.write(" NS_INTERFACE_MAP_ENTRY(%s)\n" % baseiface.name)
+ fd.write("NS_INTERFACE_MAP_END\n\n")
+
+ for a in attributes:
+ writeAttributeGetter(fd, classname, a)
+
+def get_conf(conf_file):
+ conf = Configuration(conf_file)
+ inc_dir = [
+ mozpath.join(buildconfig.topsrcdir, 'accessible', 'interfaces'),
+ mozpath.join(buildconfig.topsrcdir, 'xpcom', 'base'),
+ ]
+ return conf, inc_dir
+
+def gen_files(fd, conf_file, xpidllex, xpidlyacc):
+ deps = set()
+ conf, inc_dir = get_conf(conf_file)
+ deps.update(print_header_file(fd, conf, inc_dir))
+ with open('xpcAccEvents.cpp', 'w') as cpp_fd:
+ deps.update(print_cpp_file(cpp_fd, conf, inc_dir))
+ return deps
diff --git a/accessible/xpcom/AccEvents.conf b/accessible/xpcom/AccEvents.conf
new file mode 100644
index 000000000..0824bb78a
--- /dev/null
+++ b/accessible/xpcom/AccEvents.conf
@@ -0,0 +1,18 @@
+""" -*- Mode: 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/.
+
+ The name of the event which real interface should have nsIAccessible-prefix,
+ and should be in nsIAccessible<name>.idl file"""
+
+simple_events = [
+ 'Event',
+ 'StateChangeEvent',
+ 'TextChangeEvent',
+ 'HideEvent',
+ 'CaretMoveEvent',
+ 'ObjectAttributeChangedEvent',
+ 'TableChangeEvent',
+ 'VirtualCursorChangeEvent'
+ ]
diff --git a/accessible/xpcom/moz.build b/accessible/xpcom/moz.build
new file mode 100644
index 000000000..ec54a197a
--- /dev/null
+++ b/accessible/xpcom/moz.build
@@ -0,0 +1,66 @@
+# -*- 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/.
+
+UNIFIED_SOURCES += [
+ 'nsAccessibleRelation.cpp',
+ 'xpcAccessibilityService.cpp',
+ 'xpcAccessible.cpp',
+ 'xpcAccessibleApplication.cpp',
+ 'xpcAccessibleDocument.cpp',
+ 'xpcAccessibleGeneric.cpp',
+ 'xpcAccessibleHyperLink.cpp',
+ 'xpcAccessibleHyperText.cpp',
+ 'xpcAccessibleImage.cpp',
+ 'xpcAccessibleSelectable.cpp',
+ 'xpcAccessibleTable.cpp',
+ 'xpcAccessibleTableCell.cpp',
+ 'xpcAccessibleTextRange.cpp',
+ 'xpcAccessibleValue.cpp',
+]
+
+SOURCES += [
+ '!xpcAccEvents.cpp',
+]
+
+EXPORTS += [
+ '!xpcAccEvents.h',
+ 'xpcAccessibilityService.h',
+]
+
+LOCAL_INCLUDES += [
+ '/accessible/base',
+ '/accessible/generic',
+]
+
+if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ LOCAL_INCLUDES += [
+ '/accessible/atk',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ LOCAL_INCLUDES += [
+ '/accessible/windows/msaa',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ LOCAL_INCLUDES += [
+ '/accessible/mac',
+ ]
+else:
+ LOCAL_INCLUDES += [
+ '/accessible/other',
+ ]
+
+GENERATED_FILES += [('xpcAccEvents.h', 'xpcAccEvents.cpp')]
+
+xpc_acc = GENERATED_FILES[('xpcAccEvents.h', 'xpcAccEvents.cpp')]
+xpc_acc.script = 'AccEventGen.py:gen_files'
+xpc_acc.inputs += ['AccEvents.conf', '!/xpcom/idl-parser/xpidl/xpidllex.py', '!/xpcom/idl-parser/xpidl/xpidlyacc.py']
+
+FINAL_LIBRARY = 'xul'
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/accessible/xpcom/nsAccessibleRelation.cpp b/accessible/xpcom/nsAccessibleRelation.cpp
new file mode 100644
index 000000000..c22cb2202
--- /dev/null
+++ b/accessible/xpcom/nsAccessibleRelation.cpp
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsAccessibleRelation.h"
+
+#include "Relation.h"
+#include "Accessible.h"
+#include "xpcAccessibleDocument.h"
+
+#include "nsArrayUtils.h"
+#include "nsComponentManagerUtils.h"
+
+using namespace mozilla::a11y;
+
+nsAccessibleRelation::nsAccessibleRelation(uint32_t aType,
+ Relation* aRel) :
+ mType(aType)
+{
+ mTargets = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ Accessible* targetAcc = nullptr;
+ while ((targetAcc = aRel->Next()))
+ mTargets->AppendElement(static_cast<nsIAccessible*>(ToXPC(targetAcc)), false);
+}
+
+nsAccessibleRelation::nsAccessibleRelation(uint32_t aType,
+ const nsTArray<ProxyAccessible*>* aTargets) :
+ mType(aType)
+{
+ mTargets = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ for (uint32_t idx = 0; idx < aTargets->Length(); ++idx) {
+ mTargets->AppendElement(
+ static_cast<nsIAccessible*>(ToXPC(aTargets->ElementAt(idx))),
+ false);
+ }
+}
+
+nsAccessibleRelation::~nsAccessibleRelation()
+{
+}
+
+// nsISupports
+NS_IMPL_ISUPPORTS(nsAccessibleRelation, nsIAccessibleRelation)
+
+// nsIAccessibleRelation
+NS_IMETHODIMP
+nsAccessibleRelation::GetRelationType(uint32_t *aType)
+{
+ NS_ENSURE_ARG_POINTER(aType);
+ *aType = mType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAccessibleRelation::GetTargetsCount(uint32_t *aCount)
+{
+ NS_ENSURE_ARG_POINTER(aCount);
+ *aCount = 0;
+ return mTargets->GetLength(aCount);
+}
+
+NS_IMETHODIMP
+nsAccessibleRelation::GetTarget(uint32_t aIndex, nsIAccessible **aTarget)
+{
+ NS_ENSURE_ARG_POINTER(aTarget);
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIAccessible> target = do_QueryElementAt(mTargets, aIndex, &rv);
+ target.forget(aTarget);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAccessibleRelation::GetTargets(nsIArray **aTargets)
+{
+ NS_ENSURE_ARG_POINTER(aTargets);
+ NS_ADDREF(*aTargets = mTargets);
+ return NS_OK;
+}
diff --git a/accessible/xpcom/nsAccessibleRelation.h b/accessible/xpcom/nsAccessibleRelation.h
new file mode 100644
index 000000000..1f201d6cc
--- /dev/null
+++ b/accessible/xpcom/nsAccessibleRelation.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 _nsAccessibleRelation_H_
+#define _nsAccessibleRelation_H_
+
+#include "nsIAccessibleRelation.h"
+
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsIMutableArray.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/a11y/ProxyAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+class Relation;
+
+/**
+ * Class represents an accessible relation.
+ */
+class nsAccessibleRelation final : public nsIAccessibleRelation
+{
+public:
+ nsAccessibleRelation(uint32_t aType, Relation* aRel);
+
+ nsAccessibleRelation(uint32_t aType,
+ const nsTArray<ProxyAccessible*>* aTargets);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIACCESSIBLERELATION
+
+private:
+ nsAccessibleRelation();
+ ~nsAccessibleRelation();
+
+ nsAccessibleRelation(const nsAccessibleRelation&);
+ nsAccessibleRelation& operator = (const nsAccessibleRelation&);
+
+ uint32_t mType;
+ nsCOMPtr<nsIMutableArray> mTargets;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/xpcom/xpcAccessibilityService.cpp b/accessible/xpcom/xpcAccessibilityService.cpp
new file mode 100644
index 000000000..97c0d0e72
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibilityService.cpp
@@ -0,0 +1,246 @@
+/* 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 "xpcAccessibilityService.h"
+
+#include "nsAccessiblePivot.h"
+#include "nsAccessibilityService.h"
+
+#ifdef A11Y_LOG
+#include "Logging.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+using namespace mozilla::dom;
+
+xpcAccessibilityService *xpcAccessibilityService::gXPCAccessibilityService = nullptr;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports
+
+void
+xpcAccessibilityService::ShutdownCallback(nsITimer* aTimer, void* aClosure)
+{
+ MaybeShutdownAccService(nsAccessibilityService::eXPCOM);
+ xpcAccessibilityService* xpcAccService =
+ reinterpret_cast<xpcAccessibilityService*>(aClosure);
+
+ if (xpcAccService->mShutdownTimer) {
+ xpcAccService->mShutdownTimer->Cancel();
+ xpcAccService->mShutdownTimer = nullptr;
+ }
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+xpcAccessibilityService::AddRef(void)
+{
+ MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(xpcAccessibilityService)
+ MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");
+ if (!mRefCnt.isThreadSafe)
+ NS_ASSERT_OWNINGTHREAD(xpcAccessibilityService);
+ nsrefcnt count = ++mRefCnt;
+ NS_LOG_ADDREF(this, count, "xpcAccessibilityService", sizeof(*this));
+
+ if (mRefCnt > 1) {
+ GetOrCreateAccService(nsAccessibilityService::eXPCOM);
+ }
+
+ return count;
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+xpcAccessibilityService::Release(void)
+{
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
+
+ if (!mRefCnt.isThreadSafe) {
+ NS_ASSERT_OWNINGTHREAD(xpcAccessibilityService);
+ }
+
+ nsrefcnt count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "xpcAccessibilityService");
+
+ if (count == 0) {
+ if (!mRefCnt.isThreadSafe) {
+ NS_ASSERT_OWNINGTHREAD(xpcAccessibilityService);
+ }
+
+ mRefCnt = 1; /* stabilize */
+ delete (this);
+ return 0;
+ }
+
+ // When ref count goes down to 1 (held internally as a static reference),
+ // it means that there are no more external references to the
+ // xpcAccessibilityService and we can attempt to shut down acceessiblity
+ // service.
+ if (count == 1 && !mShutdownTimer) {
+ mShutdownTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ if (mShutdownTimer) {
+ mShutdownTimer->InitWithFuncCallback(ShutdownCallback, this, 100,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+ }
+
+ return count;
+}
+
+NS_IMPL_QUERY_INTERFACE(xpcAccessibilityService, nsIAccessibilityService,
+ nsIAccessibleRetrieval)
+
+NS_IMETHODIMP
+xpcAccessibilityService::GetApplicationAccessible(nsIAccessible** aAccessibleApplication)
+{
+ NS_ENSURE_ARG_POINTER(aAccessibleApplication);
+
+ NS_IF_ADDREF(*aAccessibleApplication = XPCApplicationAcc());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibilityService::GetAccessibleFor(nsIDOMNode *aNode,
+ nsIAccessible **aAccessible)
+{
+ NS_ENSURE_ARG_POINTER(aAccessible);
+ *aAccessible = nullptr;
+ if (!aNode) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINode> node(do_QueryInterface(aNode));
+ if (!node) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ DocAccessible* document = GetAccService()->GetDocAccessible(node->OwnerDoc());
+ if (document) {
+ NS_IF_ADDREF(*aAccessible = ToXPC(document->GetAccessible(node)));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibilityService::GetStringRole(uint32_t aRole, nsAString& aString)
+{
+ GetAccService()->GetStringRole(aRole, aString);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibilityService::GetStringStates(uint32_t aState, uint32_t aExtraState,
+ nsISupports **aStringStates)
+{
+ GetAccService()->GetStringStates(aState, aExtraState, aStringStates);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibilityService::GetStringEventType(uint32_t aEventType,
+ nsAString& aString)
+{
+ GetAccService()->GetStringEventType(aEventType, aString);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibilityService::GetStringRelationType(uint32_t aRelationType,
+ nsAString& aString)
+{
+ GetAccService()->GetStringRelationType(aRelationType, aString);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibilityService::GetAccessibleFromCache(nsIDOMNode* aNode,
+ nsIAccessible** aAccessible)
+{
+ NS_ENSURE_ARG_POINTER(aAccessible);
+ *aAccessible = nullptr;
+ if (!aNode) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINode> node(do_QueryInterface(aNode));
+ if (!node) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Search for an accessible in each of our per document accessible object
+ // caches. If we don't find it, and the given node is itself a document, check
+ // our cache of document accessibles (document cache). Note usually shutdown
+ // document accessibles are not stored in the document cache, however an
+ // "unofficially" shutdown document (i.e. not from DocManager) can still
+ // exist in the document cache.
+ Accessible* accessible = GetAccService()->FindAccessibleInCache(node);
+ if (!accessible) {
+ nsCOMPtr<nsIDocument> document(do_QueryInterface(node));
+ if (document) {
+ accessible = mozilla::a11y::GetExistingDocAccessible(document);
+ }
+ }
+
+ NS_IF_ADDREF(*aAccessible = ToXPC(accessible));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibilityService::CreateAccessiblePivot(nsIAccessible* aRoot,
+ nsIAccessiblePivot** aPivot)
+{
+ NS_ENSURE_ARG_POINTER(aPivot);
+ NS_ENSURE_ARG(aRoot);
+ *aPivot = nullptr;
+
+ Accessible* accessibleRoot = aRoot->ToInternalAccessible();
+ NS_ENSURE_TRUE(accessibleRoot, NS_ERROR_INVALID_ARG);
+
+ nsAccessiblePivot* pivot = new nsAccessiblePivot(accessibleRoot);
+ NS_ADDREF(*aPivot = pivot);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibilityService::SetLogging(const nsACString& aModules)
+{
+#ifdef A11Y_LOG
+ logging::Enable(PromiseFlatCString(aModules));
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibilityService::IsLogged(const nsAString& aModule, bool* aIsLogged)
+{
+ NS_ENSURE_ARG_POINTER(aIsLogged);
+ *aIsLogged = false;
+
+#ifdef A11Y_LOG
+ *aIsLogged = logging::IsEnabled(aModule);
+#endif
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NS_GetAccessibilityService
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult
+NS_GetAccessibilityService(nsIAccessibilityService** aResult)
+{
+ NS_ENSURE_TRUE(aResult, NS_ERROR_NULL_POINTER);
+ *aResult = nullptr;
+
+ GetOrCreateAccService(nsAccessibilityService::eXPCOM);
+
+ xpcAccessibilityService* service = new xpcAccessibilityService();
+ NS_ENSURE_TRUE(service, NS_ERROR_OUT_OF_MEMORY);
+ xpcAccessibilityService::gXPCAccessibilityService = service;
+ NS_ADDREF(*aResult = service);
+
+ return NS_OK;
+}
diff --git a/accessible/xpcom/xpcAccessibilityService.h b/accessible/xpcom/xpcAccessibilityService.h
new file mode 100644
index 000000000..76389a8a5
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibilityService.h
@@ -0,0 +1,66 @@
+/* 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_xpcAccessibilityService_h_
+#define mozilla_a11y_xpcAccessibilityService_h_
+
+#include "nsIAccessibilityService.h"
+
+class xpcAccessibilityService : public nsIAccessibleRetrieval
+{
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIACCESSIBILITYSERVICE
+ NS_DECL_NSIACCESSIBLERETRIEVAL
+
+ /**
+ * Return true if xpc accessibility service is in use.
+ */
+ static bool IsInUse() {
+ // When ref count goes down to 1 (held internally as a static reference),
+ // it means that there are no more external references and thus it is not in
+ // use.
+ return gXPCAccessibilityService ? gXPCAccessibilityService->mRefCnt > 1 : false;
+ }
+
+protected:
+ virtual ~xpcAccessibilityService() {
+ if (mShutdownTimer) {
+ mShutdownTimer->Cancel();
+ mShutdownTimer = nullptr;
+ }
+ gXPCAccessibilityService = nullptr;
+ }
+
+private:
+ // xpcAccessibilityService creation is controlled by friend
+ // NS_GetAccessibilityService, keep constructor private.
+ xpcAccessibilityService() { };
+
+ nsCOMPtr<nsITimer> mShutdownTimer;
+
+ /**
+ * Reference for xpc accessibility service instance.
+ */
+ static xpcAccessibilityService* gXPCAccessibilityService;
+
+ /**
+ * Used to shutdown nsAccessibilityService if xpcom accessible service is not
+ * in use any more.
+ */
+ static void ShutdownCallback(nsITimer* aTimer, void* aClosure);
+
+ friend nsresult NS_GetAccessibilityService(nsIAccessibilityService** aResult);
+};
+
+// for component registration
+// {3b265b69-f813-48ff-880d-d88d101af404}
+#define NS_ACCESSIBILITY_SERVICE_CID \
+{ 0x3b265b69, 0xf813, 0x48ff, { 0x88, 0x0d, 0xd8, 0x8d, 0x10, 0x1a, 0xf4, 0x04 } }
+
+extern nsresult
+NS_GetAccessibilityService(nsIAccessibilityService** aResult);
+
+#endif
diff --git a/accessible/xpcom/xpcAccessible.cpp b/accessible/xpcom/xpcAccessible.cpp
new file mode 100644
index 000000000..843b21cd2
--- /dev/null
+++ b/accessible/xpcom/xpcAccessible.cpp
@@ -0,0 +1,826 @@
+/* -*- 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 "Accessible-inl.h"
+#include "mozilla/a11y/DocAccessibleParent.h"
+#include "nsAccUtils.h"
+#include "nsIAccessibleRelation.h"
+#include "nsIAccessibleRole.h"
+#include "nsAccessibleRelation.h"
+#include "Relation.h"
+#include "Role.h"
+#include "RootAccessible.h"
+#include "xpcAccessibleDocument.h"
+
+#include "nsIMutableArray.h"
+#include "nsIPersistentProperties2.h"
+
+using namespace mozilla::a11y;
+
+NS_IMETHODIMP
+xpcAccessible::GetParent(nsIAccessible** aParent)
+{
+ NS_ENSURE_ARG_POINTER(aParent);
+ *aParent = nullptr;
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ AccessibleOrProxy parent = IntlGeneric().Parent();
+ NS_IF_ADDREF(*aParent = ToXPC(parent));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetNextSibling(nsIAccessible** aNextSibling)
+{
+ NS_ENSURE_ARG_POINTER(aNextSibling);
+ *aNextSibling = nullptr;
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (IntlGeneric().IsAccessible()) {
+ nsresult rv = NS_OK;
+ NS_IF_ADDREF(*aNextSibling = ToXPC(Intl()->GetSiblingAtOffset(1, &rv)));
+ return rv;
+ }
+
+ ProxyAccessible* proxy = IntlGeneric().AsProxy();
+ NS_ENSURE_STATE(proxy);
+
+ NS_IF_ADDREF(*aNextSibling = ToXPC(proxy->NextSibling()));
+ return *aNextSibling ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetPreviousSibling(nsIAccessible** aPreviousSibling)
+{
+ NS_ENSURE_ARG_POINTER(aPreviousSibling);
+ *aPreviousSibling = nullptr;
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (IntlGeneric().IsAccessible()) {
+ nsresult rv = NS_OK;
+ NS_IF_ADDREF(*aPreviousSibling = ToXPC(Intl()->GetSiblingAtOffset(-1, &rv)));
+ return rv;
+ }
+
+ ProxyAccessible* proxy = IntlGeneric().AsProxy();
+ NS_ENSURE_STATE(proxy);
+
+ NS_IF_ADDREF(*aPreviousSibling = ToXPC(proxy->PrevSibling()));
+ return *aPreviousSibling ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetFirstChild(nsIAccessible** aFirstChild)
+{
+ NS_ENSURE_ARG_POINTER(aFirstChild);
+ *aFirstChild = nullptr;
+
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ NS_IF_ADDREF(*aFirstChild = ToXPC(IntlGeneric().FirstChild()));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetLastChild(nsIAccessible** aLastChild)
+{
+ NS_ENSURE_ARG_POINTER(aLastChild);
+ *aLastChild = nullptr;
+
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ NS_IF_ADDREF(*aLastChild = ToXPC(IntlGeneric().LastChild()));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetChildCount(int32_t* aChildCount)
+{
+ NS_ENSURE_ARG_POINTER(aChildCount);
+
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ *aChildCount = IntlGeneric().ChildCount();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetChildAt(int32_t aChildIndex, nsIAccessible** aChild)
+{
+ NS_ENSURE_ARG_POINTER(aChild);
+ *aChild = nullptr;
+
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ // If child index is negative, then return last child.
+ // XXX: do we really need this?
+ if (aChildIndex < 0)
+ aChildIndex = IntlGeneric().ChildCount() - 1;
+
+ AccessibleOrProxy child = IntlGeneric().ChildAt(aChildIndex);
+ if (child.IsNull())
+ return NS_ERROR_INVALID_ARG;
+
+ NS_ADDREF(*aChild = ToXPC(child));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetChildren(nsIArray** aChildren)
+{
+ NS_ENSURE_ARG_POINTER(aChildren);
+ *aChildren = nullptr;
+
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMutableArray> children =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t childCount = IntlGeneric().ChildCount();
+ for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
+ AccessibleOrProxy child = IntlGeneric().ChildAt(childIdx);
+ children->AppendElement(static_cast<nsIAccessible*>(ToXPC(child)), false);
+ }
+
+ children.forget(aChildren);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetIndexInParent(int32_t* aIndexInParent)
+{
+ NS_ENSURE_ARG_POINTER(aIndexInParent);
+ *aIndexInParent = -1;
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (IntlGeneric().IsAccessible()) {
+ *aIndexInParent = Intl()->IndexInParent();
+ } else if (IntlGeneric().IsProxy()) {
+ *aIndexInParent = IntlGeneric().AsProxy()->IndexInParent();
+ }
+
+ return *aIndexInParent != -1 ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetDOMNode(nsIDOMNode** aDOMNode)
+{
+ NS_ENSURE_ARG_POINTER(aDOMNode);
+ *aDOMNode = nullptr;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ nsINode* node = Intl()->GetNode();
+ if (node)
+ CallQueryInterface(node, aDOMNode);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetId(nsAString& aID)
+{
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ ProxyAccessible* proxy = IntlGeneric().AsProxy();
+ if (!proxy) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsString id;
+ proxy->DOMNodeID(id);
+ aID.Assign(id);
+
+ return NS_OK;
+#endif
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetDocument(nsIAccessibleDocument** aDocument)
+{
+ NS_ENSURE_ARG_POINTER(aDocument);
+ *aDocument = nullptr;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ NS_IF_ADDREF(*aDocument = ToXPCDocument(Intl()->Document()));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetRootDocument(nsIAccessibleDocument** aRootDocument)
+{
+ NS_ENSURE_ARG_POINTER(aRootDocument);
+ *aRootDocument = nullptr;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ NS_IF_ADDREF(*aRootDocument = ToXPCDocument(Intl()->RootAccessible()));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetRole(uint32_t* aRole)
+{
+ NS_ENSURE_ARG_POINTER(aRole);
+ *aRole = nsIAccessibleRole::ROLE_NOTHING;
+
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ *aRole = IntlGeneric().Role();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetState(uint32_t* aState, uint32_t* aExtraState)
+{
+ NS_ENSURE_ARG_POINTER(aState);
+
+ if (IntlGeneric().IsNull())
+ nsAccUtils::To32States(states::DEFUNCT, aState, aExtraState);
+ else if (Intl())
+ nsAccUtils::To32States(Intl()->State(), aState, aExtraState);
+ else
+ nsAccUtils::To32States(IntlGeneric().AsProxy()->State(), aState,
+ aExtraState);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetName(nsAString& aName)
+{
+ aName.Truncate();
+
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ nsAutoString name;
+ if (ProxyAccessible* proxy = IntlGeneric().AsProxy()) {
+ proxy->Name(name);
+ } else {
+ Intl()->Name(name);
+ }
+
+ aName.Assign(name);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetDescription(nsAString& aDescription)
+{
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ nsAutoString desc;
+ if (ProxyAccessible* proxy = IntlGeneric().AsProxy()) {
+ proxy->Description(desc);
+ } else {
+ Intl()->Description(desc);
+ }
+
+ aDescription.Assign(desc);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetLanguage(nsAString& aLanguage)
+{
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ nsAutoString lang;
+ if (ProxyAccessible* proxy = IntlGeneric().AsProxy()) {
+ proxy->Language(lang);
+ } else {
+ Intl()->Language(lang);
+ }
+
+ aLanguage.Assign(lang);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetValue(nsAString& aValue)
+{
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ nsAutoString value;
+ if (ProxyAccessible* proxy = IntlGeneric().AsProxy()) {
+ proxy->Value(value);
+ } else {
+ Intl()->Value(value);
+ }
+
+ aValue.Assign(value);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetHelp(nsAString& aHelp)
+{
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ nsAutoString help;
+ if (ProxyAccessible* proxy = IntlGeneric().AsProxy()) {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ proxy->Help(help);
+#endif
+ } else {
+ Intl()->Help(help);
+ }
+
+ aHelp.Assign(help);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetAccessKey(nsAString& aAccessKey)
+{
+ aAccessKey.Truncate();
+
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (ProxyAccessible* proxy = IntlGeneric().AsProxy()) {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ proxy->AccessKey().ToString(aAccessKey);
+#endif
+ } else {
+ Intl()->AccessKey().ToString(aAccessKey);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetKeyboardShortcut(nsAString& aKeyBinding)
+{
+ aKeyBinding.Truncate();
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (ProxyAccessible* proxy = IntlGeneric().AsProxy()) {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ proxy->KeyboardShortcut().ToString(aKeyBinding);
+#endif
+ } else {
+ Intl()->KeyboardShortcut().ToString(aKeyBinding);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetAttributes(nsIPersistentProperties** aAttributes)
+{
+ NS_ENSURE_ARG_POINTER(aAttributes);
+ *aAttributes = nullptr;
+
+ if (IntlGeneric().IsNull()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (Accessible* acc = Intl()) {
+ nsCOMPtr<nsIPersistentProperties> attributes = acc->Attributes();
+ attributes.swap(*aAttributes);
+ return NS_OK;
+ }
+
+ ProxyAccessible* proxy = IntlGeneric().AsProxy();
+ AutoTArray<Attribute, 10> attrs;
+ proxy->Attributes(&attrs);
+
+ nsCOMPtr<nsIPersistentProperties> props =
+ do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID);
+ uint32_t attrCount = attrs.Length();
+ nsAutoString unused;
+ for (uint32_t i = 0; i < attrCount; i++) {
+ props->SetStringProperty(attrs[i].Name(), attrs[i].Value(), unused);
+ }
+
+ props.forget(aAttributes);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetBounds(int32_t* aX, int32_t* aY,
+ int32_t* aWidth, int32_t* aHeight)
+{
+ NS_ENSURE_ARG_POINTER(aX);
+ *aX = 0;
+ NS_ENSURE_ARG_POINTER(aY);
+ *aY = 0;
+ NS_ENSURE_ARG_POINTER(aWidth);
+ *aWidth = 0;
+ NS_ENSURE_ARG_POINTER(aHeight);
+ *aHeight = 0;
+
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ nsIntRect rect;
+ if (Accessible* acc = IntlGeneric().AsAccessible()) {
+ rect = acc->Bounds();
+ } else {
+ rect = IntlGeneric().AsProxy()->Bounds();
+ }
+
+ *aX = rect.x;
+ *aY = rect.y;
+ *aWidth = rect.width;
+ *aHeight = rect.height;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GroupPosition(int32_t* aGroupLevel,
+ int32_t* aSimilarItemsInGroup,
+ int32_t* aPositionInGroup)
+{
+ NS_ENSURE_ARG_POINTER(aGroupLevel);
+ *aGroupLevel = 0;
+
+ NS_ENSURE_ARG_POINTER(aSimilarItemsInGroup);
+ *aSimilarItemsInGroup = 0;
+
+ NS_ENSURE_ARG_POINTER(aPositionInGroup);
+ *aPositionInGroup = 0;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ GroupPos groupPos = Intl()->GroupPosition();
+
+ *aGroupLevel = groupPos.level;
+ *aSimilarItemsInGroup = groupPos.setSize;
+ *aPositionInGroup = groupPos.posInSet;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetRelationByType(uint32_t aType,
+ nsIAccessibleRelation** aRelation)
+{
+ NS_ENSURE_ARG_POINTER(aRelation);
+ *aRelation = nullptr;
+
+ NS_ENSURE_ARG(aType <= static_cast<uint32_t>(RelationType::LAST));
+
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (IntlGeneric().IsAccessible()) {
+ Relation rel = Intl()->RelationByType(static_cast<RelationType>(aType));
+ NS_ADDREF(*aRelation = new nsAccessibleRelation(aType, &rel));
+ return NS_OK;
+ }
+
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ ProxyAccessible* proxy = IntlGeneric().AsProxy();
+ nsTArray<ProxyAccessible*> targets =
+ proxy->RelationByType(static_cast<RelationType>(aType));
+ NS_ADDREF(*aRelation = new nsAccessibleRelation(aType, &targets));
+
+ return NS_OK;
+#endif
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetRelations(nsIArray** aRelations)
+{
+ NS_ENSURE_ARG_POINTER(aRelations);
+ *aRelations = nullptr;
+
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIMutableArray> relations = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ NS_ENSURE_TRUE(relations, NS_ERROR_OUT_OF_MEMORY);
+
+ static const uint32_t relationTypes[] = {
+ nsIAccessibleRelation::RELATION_LABELLED_BY,
+ nsIAccessibleRelation::RELATION_LABEL_FOR,
+ nsIAccessibleRelation::RELATION_DESCRIBED_BY,
+ nsIAccessibleRelation::RELATION_DESCRIPTION_FOR,
+ nsIAccessibleRelation::RELATION_NODE_CHILD_OF,
+ nsIAccessibleRelation::RELATION_NODE_PARENT_OF,
+ nsIAccessibleRelation::RELATION_CONTROLLED_BY,
+ nsIAccessibleRelation::RELATION_CONTROLLER_FOR,
+ nsIAccessibleRelation::RELATION_FLOWS_TO,
+ nsIAccessibleRelation::RELATION_FLOWS_FROM,
+ nsIAccessibleRelation::RELATION_MEMBER_OF,
+ nsIAccessibleRelation::RELATION_SUBWINDOW_OF,
+ nsIAccessibleRelation::RELATION_EMBEDS,
+ nsIAccessibleRelation::RELATION_EMBEDDED_BY,
+ nsIAccessibleRelation::RELATION_POPUP_FOR,
+ nsIAccessibleRelation::RELATION_PARENT_WINDOW_OF,
+ nsIAccessibleRelation::RELATION_DEFAULT_BUTTON,
+ nsIAccessibleRelation::RELATION_CONTAINING_DOCUMENT,
+ nsIAccessibleRelation::RELATION_CONTAINING_TAB_PANE,
+ nsIAccessibleRelation::RELATION_CONTAINING_APPLICATION
+ };
+
+ for (uint32_t idx = 0; idx < ArrayLength(relationTypes); idx++) {
+ nsCOMPtr<nsIAccessibleRelation> relation;
+ nsresult rv = GetRelationByType(relationTypes[idx], getter_AddRefs(relation));
+
+ if (NS_SUCCEEDED(rv) && relation) {
+ uint32_t targets = 0;
+ relation->GetTargetsCount(&targets);
+ if (targets)
+ relations->AppendElement(relation, false);
+ }
+ }
+
+ NS_ADDREF(*aRelations = relations);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetFocusedChild(nsIAccessible** aChild)
+{
+ NS_ENSURE_ARG_POINTER(aChild);
+ *aChild = nullptr;
+
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (ProxyAccessible* proxy = IntlGeneric().AsProxy()) {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ NS_IF_ADDREF(*aChild = ToXPC(proxy->FocusedChild()));
+#endif
+ } else {
+ NS_IF_ADDREF(*aChild = ToXPC(Intl()->FocusedChild()));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetChildAtPoint(int32_t aX, int32_t aY,
+ nsIAccessible** aAccessible)
+{
+ NS_ENSURE_ARG_POINTER(aAccessible);
+ *aAccessible = nullptr;
+
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (ProxyAccessible* proxy = IntlGeneric().AsProxy()) {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ NS_IF_ADDREF(*aAccessible =
+ ToXPC(proxy->ChildAtPoint(aX, aY, Accessible::eDirectChild)));
+#endif
+ } else {
+ NS_IF_ADDREF(*aAccessible =
+ ToXPC(Intl()->ChildAtPoint(aX, aY, Accessible::eDirectChild)));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetDeepestChildAtPoint(int32_t aX, int32_t aY,
+ nsIAccessible** aAccessible)
+{
+ NS_ENSURE_ARG_POINTER(aAccessible);
+ *aAccessible = nullptr;
+
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (ProxyAccessible* proxy = IntlGeneric().AsProxy()) {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ NS_IF_ADDREF(*aAccessible =
+ ToXPC(proxy->ChildAtPoint(aX, aY, Accessible::eDeepestChild)));
+#endif
+ } else {
+ NS_IF_ADDREF(*aAccessible =
+ ToXPC(Intl()->ChildAtPoint(aX, aY, Accessible::eDeepestChild)));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::SetSelected(bool aSelect)
+{
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (ProxyAccessible* proxy = IntlGeneric().AsProxy()) {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ proxy->SetSelected(aSelect);
+#endif
+ } else {
+ Intl()->SetSelected(aSelect);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::TakeSelection()
+{
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (ProxyAccessible* proxy = IntlGeneric().AsProxy()) {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ proxy->TakeSelection();
+#endif
+ } else {
+ Intl()->TakeSelection();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::TakeFocus()
+{
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (ProxyAccessible* proxy = IntlGeneric().AsProxy()) {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ proxy->TakeFocus();
+#endif
+ } else {
+ Intl()->TakeFocus();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetActionCount(uint8_t* aActionCount)
+{
+ NS_ENSURE_ARG_POINTER(aActionCount);
+ *aActionCount = 0;
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (ProxyAccessible* proxy = IntlGeneric().AsProxy()) {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ *aActionCount = proxy->ActionCount();
+#endif
+ } else {
+ *aActionCount = Intl()->ActionCount();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetActionName(uint8_t aIndex, nsAString& aName)
+{
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (ProxyAccessible* proxy = IntlGeneric().AsProxy()) {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ nsString name;
+ proxy->ActionNameAt(aIndex, name);
+ aName.Assign(name);
+#endif
+ } else {
+ if (aIndex >= Intl()->ActionCount())
+ return NS_ERROR_INVALID_ARG;
+
+ Intl()->ActionNameAt(aIndex, aName);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::GetActionDescription(uint8_t aIndex, nsAString& aDescription)
+{
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (ProxyAccessible* proxy = IntlGeneric().AsProxy()) {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ nsString description;
+ proxy->ActionDescriptionAt(aIndex, description);
+ aDescription.Assign(description);
+#endif
+ } else {
+ if (aIndex >= Intl()->ActionCount())
+ return NS_ERROR_INVALID_ARG;
+
+ Intl()->ActionDescriptionAt(aIndex, aDescription);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::DoAction(uint8_t aIndex)
+{
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (ProxyAccessible* proxy = IntlGeneric().AsProxy()) {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ return proxy->DoAction(aIndex) ? NS_OK : NS_ERROR_INVALID_ARG;
+#endif
+ } else {
+ return Intl()->DoAction(aIndex) ?
+ NS_OK : NS_ERROR_INVALID_ARG;
+ }
+}
+
+NS_IMETHODIMP
+xpcAccessible::ScrollTo(uint32_t aHow)
+{
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (ProxyAccessible* proxy = IntlGeneric().AsProxy()) {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ proxy->ScrollTo(aHow);
+#endif
+ } else {
+ Intl()->ScrollTo(aHow);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessible::ScrollToPoint(uint32_t aCoordinateType, int32_t aX, int32_t aY)
+{
+ if (IntlGeneric().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (ProxyAccessible* proxy = IntlGeneric().AsProxy()) {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ proxy->ScrollToPoint(aCoordinateType, aX, aY);
+#endif
+ } else {
+ Intl()->ScrollToPoint(aCoordinateType, aX, aY);
+ }
+
+ return NS_OK;
+}
diff --git a/accessible/xpcom/xpcAccessible.h b/accessible/xpcom/xpcAccessible.h
new file mode 100644
index 000000000..cecc72720
--- /dev/null
+++ b/accessible/xpcom/xpcAccessible.h
@@ -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/. */
+
+#ifndef mozilla_a11y_xpcAccessible_h_
+#define mozilla_a11y_xpcAccessible_h_
+
+#include "nsIAccessible.h"
+
+class nsIAccessible;
+
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+class AccessibleOrProxy;
+
+/**
+ * XPCOM nsIAccessible interface implementation, used by xpcAccessibleGeneric
+ * class.
+ */
+class xpcAccessible : public nsIAccessible
+{
+public:
+ // nsIAccessible
+ NS_IMETHOD GetParent(nsIAccessible** aParent) final override;
+ NS_IMETHOD GetNextSibling(nsIAccessible** aNextSibling) final override;
+ NS_IMETHOD GetPreviousSibling(nsIAccessible** aPreviousSibling)
+ final override;
+ NS_IMETHOD GetFirstChild(nsIAccessible** aFirstChild) final override;
+ NS_IMETHOD GetLastChild(nsIAccessible** aLastChild) final override;
+ NS_IMETHOD GetChildCount(int32_t* aChildCount) final override;
+ NS_IMETHOD GetChildAt(int32_t aChildIndex, nsIAccessible** aChild)
+ final override;
+ NS_IMETHOD GetChildren(nsIArray** aChildren) final override;
+ NS_IMETHOD GetIndexInParent(int32_t* aIndexInParent) final override;
+
+ NS_IMETHOD GetDOMNode(nsIDOMNode** aDOMNode) final override;
+ NS_IMETHOD GetId(nsAString& aID) final override;
+ NS_IMETHOD GetDocument(nsIAccessibleDocument** aDocument) final override;
+ NS_IMETHOD GetRootDocument(nsIAccessibleDocument** aRootDocument)
+ final override;
+
+ NS_IMETHOD GetRole(uint32_t* aRole) final override;
+ NS_IMETHOD GetState(uint32_t* aState, uint32_t* aExtraState)
+ final override;
+
+ NS_IMETHOD GetDescription(nsAString& aDescription) final override;
+ NS_IMETHOD GetName(nsAString& aName) final override;
+ NS_IMETHOD GetLanguage(nsAString& aLanguage) final override;
+ NS_IMETHOD GetValue(nsAString& aValue) final override;
+ NS_IMETHOD GetHelp(nsAString& aHelp) final override;
+
+ NS_IMETHOD GetAccessKey(nsAString& aAccessKey) final override;
+ NS_IMETHOD GetKeyboardShortcut(nsAString& aKeyBinding) final override;
+
+ NS_IMETHOD GetAttributes(nsIPersistentProperties** aAttributes)
+ final override;
+ NS_IMETHOD GetBounds(int32_t* aX, int32_t* aY,
+ int32_t* aWidth, int32_t* aHeight) final override;
+ NS_IMETHOD GroupPosition(int32_t* aGroupLevel, int32_t* aSimilarItemsInGroup,
+ int32_t* aPositionInGroup) final override;
+ NS_IMETHOD GetRelationByType(uint32_t aType,
+ nsIAccessibleRelation** aRelation)
+ final override;
+ NS_IMETHOD GetRelations(nsIArray** aRelations) final override;
+
+ NS_IMETHOD GetFocusedChild(nsIAccessible** aChild) final override;
+ NS_IMETHOD GetChildAtPoint(int32_t aX, int32_t aY,
+ nsIAccessible** aAccessible) final override;
+ NS_IMETHOD GetDeepestChildAtPoint(int32_t aX, int32_t aY,
+ nsIAccessible** aAccessible)
+ final override;
+
+ NS_IMETHOD SetSelected(bool aSelect) final override;
+ NS_IMETHOD TakeSelection() final override;
+ NS_IMETHOD TakeFocus() final override;
+
+ NS_IMETHOD GetActionCount(uint8_t* aActionCount) final override;
+ NS_IMETHOD GetActionName(uint8_t aIndex, nsAString& aName) final override;
+ NS_IMETHOD GetActionDescription(uint8_t aIndex, nsAString& aDescription)
+ final override;
+ NS_IMETHOD DoAction(uint8_t aIndex) final override;
+
+ NS_IMETHOD ScrollTo(uint32_t aHow) final override;
+ NS_IMETHOD ScrollToPoint(uint32_t aCoordinateType,
+ int32_t aX, int32_t aY) final override;
+
+protected:
+ xpcAccessible() { }
+ virtual ~xpcAccessible() {}
+
+private:
+ Accessible* Intl();
+ AccessibleOrProxy IntlGeneric();
+
+ xpcAccessible(const xpcAccessible&) = delete;
+ xpcAccessible& operator =(const xpcAccessible&) = delete;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/xpcom/xpcAccessibleApplication.cpp b/accessible/xpcom/xpcAccessibleApplication.cpp
new file mode 100644
index 000000000..af2bde466
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibleApplication.cpp
@@ -0,0 +1,69 @@
+/* -*- 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 "xpcAccessibleApplication.h"
+
+#include "ApplicationAccessible.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED(xpcAccessibleApplication,
+ xpcAccessibleGeneric,
+ nsIAccessibleApplication)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIAccessibleApplication
+
+NS_IMETHODIMP
+xpcAccessibleApplication::GetAppName(nsAString& aName)
+{
+ aName.Truncate();
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ Intl()->AppName(aName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleApplication::GetAppVersion(nsAString& aVersion)
+{
+ aVersion.Truncate();
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ Intl()->AppVersion(aVersion);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleApplication::GetPlatformName(nsAString& aName)
+{
+ aName.Truncate();
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ Intl()->PlatformName(aName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleApplication::GetPlatformVersion(nsAString& aVersion)
+{
+ aVersion.Truncate();
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ Intl()->PlatformVersion(aVersion);
+ return NS_OK;
+}
diff --git a/accessible/xpcom/xpcAccessibleApplication.h b/accessible/xpcom/xpcAccessibleApplication.h
new file mode 100644
index 000000000..24edbcbdb
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibleApplication.h
@@ -0,0 +1,48 @@
+/* -*- 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_xpcAccessibleApplication_h_
+#define mozilla_a11y_xpcAccessibleApplication_h_
+
+#include "nsIAccessibleApplication.h"
+#include "ApplicationAccessible.h"
+#include "xpcAccessibleGeneric.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * XPCOM wrapper around ApplicationAccessible class.
+ */
+class xpcAccessibleApplication : public xpcAccessibleGeneric,
+ public nsIAccessibleApplication
+{
+public:
+ explicit xpcAccessibleApplication(Accessible* aIntl) :
+ xpcAccessibleGeneric(aIntl) { }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIAccessibleApplication
+ NS_IMETHOD GetAppName(nsAString& aName) final override;
+ NS_IMETHOD GetAppVersion(nsAString& aVersion) final override;
+ NS_IMETHOD GetPlatformName(nsAString& aName) final override;
+ NS_IMETHOD GetPlatformVersion(nsAString& aVersion) final override;
+
+protected:
+ virtual ~xpcAccessibleApplication() {}
+
+private:
+ ApplicationAccessible* Intl() { return mIntl.AsAccessible()->AsApplication(); }
+
+ xpcAccessibleApplication(const xpcAccessibleApplication&) = delete;
+ xpcAccessibleApplication& operator =(const xpcAccessibleApplication&) = delete;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/xpcom/xpcAccessibleDocument.cpp b/accessible/xpcom/xpcAccessibleDocument.cpp
new file mode 100644
index 000000000..055cb8a86
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibleDocument.cpp
@@ -0,0 +1,262 @@
+/* -*- 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 "xpcAccessibleDocument.h"
+#include "xpcAccessibleImage.h"
+#include "xpcAccessibleTable.h"
+#include "xpcAccessibleTableCell.h"
+
+#include "mozilla/a11y/DocAccessibleParent.h"
+#include "DocAccessible-inl.h"
+#include "nsIDOMDocument.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports and cycle collection
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(xpcAccessibleDocument)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(xpcAccessibleDocument,
+ xpcAccessibleGeneric)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCache)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(xpcAccessibleDocument,
+ xpcAccessibleGeneric)
+ tmp->mCache.Clear();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(xpcAccessibleDocument)
+ NS_INTERFACE_MAP_ENTRY(nsIAccessibleDocument)
+NS_INTERFACE_MAP_END_INHERITING(xpcAccessibleHyperText)
+
+NS_IMPL_ADDREF_INHERITED(xpcAccessibleDocument, xpcAccessibleHyperText)
+NS_IMPL_RELEASE_INHERITED(xpcAccessibleDocument, xpcAccessibleHyperText)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIAccessibleDocument
+
+NS_IMETHODIMP
+xpcAccessibleDocument::GetURL(nsAString& aURL)
+{
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ Intl()->URL(aURL);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleDocument::GetTitle(nsAString& aTitle)
+{
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ nsAutoString title;
+ Intl()->Title(title);
+ aTitle = title;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleDocument::GetMimeType(nsAString& aType)
+{
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ Intl()->MimeType(aType);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleDocument::GetDocType(nsAString& aType)
+{
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ Intl()->DocType(aType);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleDocument::GetDOMDocument(nsIDOMDocument** aDOMDocument)
+{
+ NS_ENSURE_ARG_POINTER(aDOMDocument);
+ *aDOMDocument = nullptr;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ if (Intl()->DocumentNode())
+ CallQueryInterface(Intl()->DocumentNode(), aDOMDocument);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleDocument::GetWindow(mozIDOMWindowProxy** aDOMWindow)
+{
+ NS_ENSURE_ARG_POINTER(aDOMWindow);
+ *aDOMWindow = nullptr;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ NS_IF_ADDREF(*aDOMWindow = Intl()->DocumentNode()->GetWindow());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleDocument::GetParentDocument(nsIAccessibleDocument** aDocument)
+{
+ NS_ENSURE_ARG_POINTER(aDocument);
+ *aDocument = nullptr;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ NS_IF_ADDREF(*aDocument = ToXPCDocument(Intl()->ParentDocument()));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleDocument::GetChildDocumentCount(uint32_t* aCount)
+{
+ NS_ENSURE_ARG_POINTER(aCount);
+ *aCount = 0;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ *aCount = Intl()->ChildDocumentCount();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleDocument::GetChildDocumentAt(uint32_t aIndex,
+ nsIAccessibleDocument** aDocument)
+{
+ NS_ENSURE_ARG_POINTER(aDocument);
+ *aDocument = nullptr;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ NS_IF_ADDREF(*aDocument = ToXPCDocument(Intl()->GetChildDocumentAt(aIndex)));
+ return *aDocument ? NS_OK : NS_ERROR_INVALID_ARG;
+}
+
+NS_IMETHODIMP
+xpcAccessibleDocument::GetVirtualCursor(nsIAccessiblePivot** aVirtualCursor)
+{
+ NS_ENSURE_ARG_POINTER(aVirtualCursor);
+ *aVirtualCursor = nullptr;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ NS_ADDREF(*aVirtualCursor = Intl()->VirtualCursor());
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// xpcAccessibleDocument
+
+xpcAccessibleGeneric*
+xpcAccessibleDocument::GetAccessible(Accessible* aAccessible)
+{
+ MOZ_ASSERT(!mRemote);
+ if (ToXPCDocument(aAccessible->Document()) != this) {
+ NS_ERROR("This XPCOM document is not related with given internal accessible!");
+ return nullptr;
+ }
+
+ if (aAccessible->IsDoc())
+ return this;
+
+ xpcAccessibleGeneric* xpcAcc = mCache.GetWeak(aAccessible);
+ if (xpcAcc)
+ return xpcAcc;
+
+ if (aAccessible->IsImage())
+ xpcAcc = new xpcAccessibleImage(aAccessible);
+ else if (aAccessible->IsTable())
+ xpcAcc = new xpcAccessibleTable(aAccessible);
+ else if (aAccessible->IsTableCell())
+ xpcAcc = new xpcAccessibleTableCell(aAccessible);
+ else if (aAccessible->IsHyperText())
+ xpcAcc = new xpcAccessibleHyperText(aAccessible);
+ else
+ xpcAcc = new xpcAccessibleGeneric(aAccessible);
+
+ mCache.Put(aAccessible, xpcAcc);
+ return xpcAcc;
+}
+
+xpcAccessibleGeneric*
+xpcAccessibleDocument::GetXPCAccessible(ProxyAccessible* aProxy)
+{
+ MOZ_ASSERT(mRemote);
+ MOZ_ASSERT(aProxy->Document() == mIntl.AsProxy());
+ if (aProxy->IsDoc()) {
+ return this;
+ }
+
+ xpcAccessibleGeneric* acc = mCache.GetWeak(aProxy);
+ if (acc) {
+ return acc;
+ }
+
+ // XXX support exposing optional interfaces.
+ uint8_t interfaces = 0;
+ if (aProxy->mHasValue) {
+ interfaces |= eValue;
+ }
+
+ if (aProxy->mIsHyperLink) {
+ interfaces |= eHyperLink;
+ }
+
+ if (aProxy->mIsHyperText) {
+ interfaces |= eText;
+ acc = new xpcAccessibleHyperText(aProxy, interfaces);
+ mCache.Put(aProxy, acc);
+
+ return acc;
+ }
+
+ acc = new xpcAccessibleGeneric(aProxy, interfaces);
+ mCache.Put(aProxy, acc);
+
+ return acc;
+}
+
+void
+xpcAccessibleDocument::Shutdown()
+{
+ for (auto iter = mCache.Iter(); !iter.Done(); iter.Next()) {
+ iter.Data()->Shutdown();
+ iter.Remove();
+ }
+ xpcAccessibleGeneric::Shutdown();
+}
+
+xpcAccessibleGeneric*
+a11y::ToXPC(AccessibleOrProxy aAcc)
+{
+ if (aAcc.IsNull()) {
+ return nullptr;
+ }
+
+ if (aAcc.IsAccessible()) {
+ return ToXPC(aAcc.AsAccessible());
+ }
+
+ xpcAccessibleDocument* doc = ToXPCDocument(aAcc.AsProxy()->Document());
+ return doc->GetXPCAccessible(aAcc.AsProxy());
+}
diff --git a/accessible/xpcom/xpcAccessibleDocument.h b/accessible/xpcom/xpcAccessibleDocument.h
new file mode 100644
index 000000000..651fe2255
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibleDocument.h
@@ -0,0 +1,150 @@
+/* -*- 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_xpcAccessibleDocument_h_
+#define mozilla_a11y_xpcAccessibleDocument_h_
+
+#include "nsIAccessibleDocument.h"
+
+#include "DocAccessible.h"
+#include "nsAccessibilityService.h"
+#include "xpcAccessibleApplication.h"
+#include "xpcAccessibleHyperText.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * XPCOM wrapper around DocAccessible class.
+ */
+class xpcAccessibleDocument : public xpcAccessibleHyperText,
+ public nsIAccessibleDocument
+{
+public:
+ explicit xpcAccessibleDocument(DocAccessible* aIntl) :
+ xpcAccessibleHyperText(aIntl), mCache(kDefaultCacheLength), mRemote(false) { }
+
+ xpcAccessibleDocument(ProxyAccessible* aProxy, uint32_t aInterfaces) :
+ xpcAccessibleHyperText(aProxy, aInterfaces), mCache(kDefaultCacheLength),
+ mRemote(true) {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(xpcAccessibleDocument,
+ xpcAccessibleGeneric)
+
+ // nsIAccessibleDocument
+ NS_IMETHOD GetURL(nsAString& aURL) final override;
+ NS_IMETHOD GetTitle(nsAString& aTitle) final override;
+ NS_IMETHOD GetMimeType(nsAString& aType) final override;
+ NS_IMETHOD GetDocType(nsAString& aType) final override;
+ NS_IMETHOD GetDOMDocument(nsIDOMDocument** aDOMDocument) final override;
+ NS_IMETHOD GetWindow(mozIDOMWindowProxy** aDOMWindow) final override;
+ NS_IMETHOD GetParentDocument(nsIAccessibleDocument** aDocument)
+ final override;
+ NS_IMETHOD GetChildDocumentCount(uint32_t* aCount) final override;
+ NS_IMETHOD GetChildDocumentAt(uint32_t aIndex,
+ nsIAccessibleDocument** aDocument)
+ final override;
+ NS_IMETHOD GetVirtualCursor(nsIAccessiblePivot** aVirtualCursor)
+ final override;
+
+ /**
+ * Return XPCOM wrapper for the internal accessible.
+ */
+ xpcAccessibleGeneric* GetAccessible(Accessible* aAccessible);
+ xpcAccessibleGeneric* GetXPCAccessible(ProxyAccessible* aProxy);
+
+ virtual void Shutdown() override;
+
+protected:
+ virtual ~xpcAccessibleDocument() {}
+
+private:
+ DocAccessible* Intl()
+ {
+ if (Accessible* acc = mIntl.AsAccessible()) {
+ return acc->AsDoc();
+ }
+
+ return nullptr;
+ }
+
+ void NotifyOfShutdown(Accessible* aAccessible)
+ {
+ MOZ_ASSERT(!mRemote);
+ xpcAccessibleGeneric* xpcAcc = mCache.GetWeak(aAccessible);
+ if (xpcAcc)
+ xpcAcc->Shutdown();
+
+ mCache.Remove(aAccessible);
+ }
+
+ void NotifyOfShutdown(ProxyAccessible* aProxy)
+ {
+ MOZ_ASSERT(mRemote);
+ xpcAccessibleGeneric* acc = mCache.GetWeak(aProxy);
+ if (acc) {
+ acc->Shutdown();
+ }
+
+ mCache.Remove(aProxy);
+ }
+
+ friend class DocManager;
+ friend class DocAccessible;
+ friend class ProxyAccessible;
+ friend class ProxyAccessibleBase<ProxyAccessible>;
+
+ xpcAccessibleDocument(const xpcAccessibleDocument&) = delete;
+ xpcAccessibleDocument& operator =(const xpcAccessibleDocument&) = delete;
+
+ nsRefPtrHashtable<nsPtrHashKey<const void>, xpcAccessibleGeneric> mCache;
+ bool mRemote;
+};
+
+inline xpcAccessibleGeneric*
+ToXPC(Accessible* aAccessible)
+{
+ if (!aAccessible)
+ return nullptr;
+
+ if (aAccessible->IsApplication())
+ return XPCApplicationAcc();
+
+ xpcAccessibleDocument* xpcDoc =
+ GetAccService()->GetXPCDocument(aAccessible->Document());
+ return xpcDoc ? xpcDoc->GetAccessible(aAccessible) : nullptr;
+}
+
+xpcAccessibleGeneric* ToXPC(AccessibleOrProxy aAcc);
+
+inline xpcAccessibleHyperText*
+ToXPCText(HyperTextAccessible* aAccessible)
+{
+ if (!aAccessible)
+ return nullptr;
+
+ xpcAccessibleDocument* xpcDoc =
+ GetAccService()->GetXPCDocument(aAccessible->Document());
+ return static_cast<xpcAccessibleHyperText*>(xpcDoc->GetAccessible(aAccessible));
+}
+
+inline xpcAccessibleDocument*
+ToXPCDocument(DocAccessible* aAccessible)
+{
+ return GetAccService()->GetXPCDocument(aAccessible);
+}
+
+inline xpcAccessibleDocument*
+ToXPCDocument(DocAccessibleParent* aAccessible)
+{
+ return GetAccService()->GetXPCDocument(aAccessible);
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/xpcom/xpcAccessibleGeneric.cpp b/accessible/xpcom/xpcAccessibleGeneric.cpp
new file mode 100644
index 000000000..5793b8a2e
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibleGeneric.cpp
@@ -0,0 +1,46 @@
+/* -*- 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 "xpcAccessibleGeneric.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports and cycle collection
+
+NS_IMPL_CYCLE_COLLECTION_0(xpcAccessibleGeneric)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(xpcAccessibleGeneric)
+ NS_INTERFACE_MAP_ENTRY(nsIAccessible)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAccessibleSelectable,
+ mSupportedIfaces & eSelectable)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAccessibleValue,
+ mSupportedIfaces & eValue)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAccessibleHyperLink,
+ mSupportedIfaces & eHyperLink)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAccessible)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(xpcAccessibleGeneric)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(xpcAccessibleGeneric)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIAccessible
+
+Accessible*
+xpcAccessibleGeneric::ToInternalAccessible() const
+{
+ return mIntl.AsAccessible();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// xpcAccessibleGeneric
+
+void
+xpcAccessibleGeneric::Shutdown()
+{
+ mIntl = nullptr;
+}
diff --git a/accessible/xpcom/xpcAccessibleGeneric.h b/accessible/xpcom/xpcAccessibleGeneric.h
new file mode 100644
index 000000000..da51d2728
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibleGeneric.h
@@ -0,0 +1,110 @@
+/* -*- 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_xpcAccessibleGeneric_h_
+#define mozilla_a11y_xpcAccessibleGeneric_h_
+
+#include "xpcAccessible.h"
+#include "xpcAccessibleHyperLink.h"
+#include "xpcAccessibleSelectable.h"
+#include "xpcAccessibleValue.h"
+
+#include "Accessible.h"
+#include "AccessibleOrProxy.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * XPCOM wrapper around Accessible class.
+ */
+class xpcAccessibleGeneric : public xpcAccessible,
+ public xpcAccessibleHyperLink,
+ public xpcAccessibleSelectable,
+ public xpcAccessibleValue
+{
+public:
+ explicit xpcAccessibleGeneric(Accessible* aInternal) :
+ mIntl(aInternal), mSupportedIfaces(0)
+ {
+ if (aInternal->IsSelect())
+ mSupportedIfaces |= eSelectable;
+ if (aInternal->HasNumericValue())
+ mSupportedIfaces |= eValue;
+ if (aInternal->IsLink())
+ mSupportedIfaces |= eHyperLink;
+ }
+
+ xpcAccessibleGeneric(ProxyAccessible* aProxy, uint8_t aInterfaces) :
+ mIntl(aProxy), mSupportedIfaces(aInterfaces) {}
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(xpcAccessibleGeneric, nsIAccessible)
+
+ // nsIAccessible
+ virtual Accessible* ToInternalAccessible() const final override;
+
+ // xpcAccessibleGeneric
+ virtual void Shutdown();
+
+protected:
+ virtual ~xpcAccessibleGeneric() {}
+
+ AccessibleOrProxy mIntl;
+
+ enum {
+ eSelectable = 1 << 0,
+ eValue = 1 << 1,
+ eHyperLink = 1 << 2,
+ eText = 1 << 3
+ };
+ uint8_t mSupportedIfaces;
+
+private:
+ friend class Accessible;
+ friend class xpcAccessible;
+ friend class xpcAccessibleHyperLink;
+ friend class xpcAccessibleSelectable;
+ friend class xpcAccessibleValue;
+
+ xpcAccessibleGeneric(const xpcAccessibleGeneric&) = delete;
+ xpcAccessibleGeneric& operator =(const xpcAccessibleGeneric&) = delete;
+};
+
+inline Accessible*
+xpcAccessible::Intl()
+{
+ return static_cast<xpcAccessibleGeneric*>(this)->mIntl.AsAccessible();
+}
+
+inline AccessibleOrProxy
+xpcAccessible::IntlGeneric()
+{
+ return static_cast<xpcAccessibleGeneric*>(this)->mIntl;
+}
+
+inline AccessibleOrProxy
+xpcAccessibleHyperLink::Intl()
+{
+ return static_cast<xpcAccessibleGeneric*>(this)->mIntl;
+}
+
+inline Accessible*
+xpcAccessibleSelectable::Intl()
+{
+ return static_cast<xpcAccessibleGeneric*>(this)->mIntl.AsAccessible();
+}
+
+inline AccessibleOrProxy
+xpcAccessibleValue::Intl()
+{
+ return static_cast<xpcAccessibleGeneric*>(this)->mIntl;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/xpcom/xpcAccessibleHyperLink.cpp b/accessible/xpcom/xpcAccessibleHyperLink.cpp
new file mode 100644
index 000000000..644f355f0
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibleHyperLink.cpp
@@ -0,0 +1,180 @@
+/* -*- 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 "Accessible-inl.h"
+#include "xpcAccessibleDocument.h"
+#include "nsNetUtil.h"
+
+using namespace mozilla::a11y;
+
+NS_IMETHODIMP
+xpcAccessibleHyperLink::GetStartIndex(int32_t* aStartIndex)
+{
+ NS_ENSURE_ARG_POINTER(aStartIndex);
+ *aStartIndex = 0;
+
+ if (Intl().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (Intl().IsAccessible()) {
+ *aStartIndex = Intl().AsAccessible()->StartOffset();
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ bool isIndexValid = false;
+ uint32_t startOffset = Intl().AsProxy()->StartOffset(&isIndexValid);
+ if (!isIndexValid)
+ return NS_ERROR_FAILURE;
+
+ *aStartIndex = startOffset;
+#endif
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperLink::GetEndIndex(int32_t* aEndIndex)
+{
+ NS_ENSURE_ARG_POINTER(aEndIndex);
+ *aEndIndex = 0;
+
+ if (Intl().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (Intl().IsAccessible()) {
+ *aEndIndex = Intl().AsAccessible()->EndOffset();
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ bool isIndexValid = false;
+ uint32_t endOffset = Intl().AsProxy()->EndOffset(&isIndexValid);
+ if (!isIndexValid)
+ return NS_ERROR_FAILURE;
+
+ *aEndIndex = endOffset;
+#endif
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperLink::GetAnchorCount(int32_t* aAnchorCount)
+{
+ NS_ENSURE_ARG_POINTER(aAnchorCount);
+ *aAnchorCount = 0;
+
+ if (Intl().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (Intl().IsAccessible()) {
+ *aAnchorCount = Intl().AsAccessible()->AnchorCount();
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ bool isCountValid = false;
+ uint32_t anchorCount = Intl().AsProxy()->AnchorCount(&isCountValid);
+ if (!isCountValid)
+ return NS_ERROR_FAILURE;
+
+ *aAnchorCount = anchorCount;
+#endif
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperLink::GetURI(int32_t aIndex, nsIURI** aURI)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ if (Intl().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (aIndex < 0)
+ return NS_ERROR_INVALID_ARG;
+
+ if (Intl().IsAccessible()) {
+ if (aIndex >= static_cast<int32_t>(Intl().AsAccessible()->AnchorCount()))
+ return NS_ERROR_INVALID_ARG;
+
+ RefPtr<nsIURI>(Intl().AsAccessible()->AnchorURIAt(aIndex)).forget(aURI);
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ nsCString spec;
+ bool isURIValid = false;
+ Intl().AsProxy()->AnchorURIAt(aIndex, spec, &isURIValid);
+ if (!isURIValid)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uri.forget(aURI);
+#endif
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+xpcAccessibleHyperLink::GetAnchor(int32_t aIndex, nsIAccessible** aAccessible)
+{
+ NS_ENSURE_ARG_POINTER(aAccessible);
+ *aAccessible = nullptr;
+
+ if (Intl().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (aIndex < 0)
+ return NS_ERROR_INVALID_ARG;
+
+ if (Intl().IsAccessible()) {
+ if (aIndex >= static_cast<int32_t>(Intl().AsAccessible()->AnchorCount()))
+ return NS_ERROR_INVALID_ARG;
+
+ NS_IF_ADDREF(*aAccessible = ToXPC(Intl().AsAccessible()->AnchorAt(aIndex)));
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ NS_IF_ADDREF(*aAccessible = ToXPC(Intl().AsProxy()->AnchorAt(aIndex)));
+#endif
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperLink::GetValid(bool* aValid)
+{
+ NS_ENSURE_ARG_POINTER(aValid);
+ *aValid = false;
+
+ if (Intl().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (Intl().IsAccessible()) {
+ *aValid = Intl().AsAccessible()->IsLinkValid();
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ *aValid = Intl().AsProxy()->IsLinkValid();
+#endif
+ }
+
+ return NS_OK;
+}
diff --git a/accessible/xpcom/xpcAccessibleHyperLink.h b/accessible/xpcom/xpcAccessibleHyperLink.h
new file mode 100644
index 000000000..0f986cfd4
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibleHyperLink.h
@@ -0,0 +1,48 @@
+/* -*- 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_xpcAccessibleHyperLink_h_
+#define mozilla_a11y_xpcAccessibleHyperLink_h_
+
+#include "nsIAccessibleHyperLink.h"
+
+class nsIAccessible;
+
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+
+/**
+ * XPCOM nsIAccessibleHyperLink implementation, used by xpcAccessibleGeneric
+ * class.
+ */
+class xpcAccessibleHyperLink : public nsIAccessibleHyperLink
+{
+public:
+ NS_IMETHOD GetAnchorCount(int32_t* aAnchorCount) final override;
+ NS_IMETHOD GetStartIndex(int32_t* aStartIndex) final override;
+ NS_IMETHOD GetEndIndex(int32_t* aEndIndex) final override;
+ NS_IMETHOD GetURI(int32_t aIndex, nsIURI** aURI) final override;
+ NS_IMETHOD GetAnchor(int32_t aIndex, nsIAccessible** aAccessible)
+ final override;
+ NS_IMETHOD GetValid(bool* aValid) final override;
+
+protected:
+ xpcAccessibleHyperLink() { }
+ virtual ~xpcAccessibleHyperLink() {}
+
+private:
+ xpcAccessibleHyperLink(const xpcAccessibleHyperLink&) = delete;
+ xpcAccessibleHyperLink& operator =(const xpcAccessibleHyperLink&) = delete;
+
+ AccessibleOrProxy Intl();
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/xpcom/xpcAccessibleHyperText.cpp b/accessible/xpcom/xpcAccessibleHyperText.cpp
new file mode 100644
index 000000000..b31544ac7
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibleHyperText.cpp
@@ -0,0 +1,822 @@
+/* -*- 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 "xpcAccessibleHyperText.h"
+
+#include "Accessible-inl.h"
+#include "HyperTextAccessible-inl.h"
+#include "TextRange.h"
+#include "xpcAccessibleDocument.h"
+#include "xpcAccessibleTextRange.h"
+
+#include "nsIPersistentProperties2.h"
+#include "nsIMutableArray.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports
+
+NS_INTERFACE_MAP_BEGIN(xpcAccessibleHyperText)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAccessibleText,
+ mSupportedIfaces & eText)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAccessibleEditableText,
+ mSupportedIfaces & eText)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAccessibleHyperText,
+ mSupportedIfaces & eText)
+NS_INTERFACE_MAP_END_INHERITING(xpcAccessibleGeneric)
+
+NS_IMPL_ADDREF_INHERITED(xpcAccessibleHyperText, xpcAccessibleGeneric)
+NS_IMPL_RELEASE_INHERITED(xpcAccessibleHyperText, xpcAccessibleGeneric)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIAccessibleText
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::GetCharacterCount(int32_t* aCharacterCount)
+{
+ NS_ENSURE_ARG_POINTER(aCharacterCount);
+ *aCharacterCount = 0;
+
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (mIntl.IsAccessible()) {
+ *aCharacterCount = Intl()->CharacterCount();
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ *aCharacterCount = mIntl.AsProxy()->CharacterCount();
+#endif
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::GetText(int32_t aStartOffset, int32_t aEndOffset,
+ nsAString& aText)
+{
+ aText.Truncate();
+
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (mIntl.IsAccessible()) {
+ Intl()->TextSubstring(aStartOffset, aEndOffset, aText);
+ } else {
+ nsString text;
+ mIntl.AsProxy()->TextSubstring(aStartOffset, aEndOffset, text);
+ aText = text;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::GetTextBeforeOffset(int32_t aOffset,
+ AccessibleTextBoundary aBoundaryType,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset,
+ nsAString& aText)
+{
+ NS_ENSURE_ARG_POINTER(aStartOffset);
+ NS_ENSURE_ARG_POINTER(aEndOffset);
+ *aStartOffset = *aEndOffset = 0;
+ aText.Truncate();
+
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (mIntl.IsAccessible()) {
+ Intl()->TextBeforeOffset(aOffset, aBoundaryType, aStartOffset, aEndOffset,
+ aText);
+ } else {
+ nsString text;
+ mIntl.AsProxy()->GetTextBeforeOffset(aOffset, aBoundaryType, text,
+ aStartOffset, aEndOffset);
+ aText = text;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::GetTextAtOffset(int32_t aOffset,
+ AccessibleTextBoundary aBoundaryType,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset, nsAString& aText)
+{
+ NS_ENSURE_ARG_POINTER(aStartOffset);
+ NS_ENSURE_ARG_POINTER(aEndOffset);
+ *aStartOffset = *aEndOffset = 0;
+ aText.Truncate();
+
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (mIntl.IsAccessible()) {
+ Intl()->TextAtOffset(aOffset, aBoundaryType, aStartOffset, aEndOffset,
+ aText);
+ } else {
+ nsString text;
+ mIntl.AsProxy()->GetTextAtOffset(aOffset, aBoundaryType, text,
+ aStartOffset, aEndOffset);
+ aText = text;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::GetTextAfterOffset(int32_t aOffset,
+ AccessibleTextBoundary aBoundaryType,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset, nsAString& aText)
+{
+ NS_ENSURE_ARG_POINTER(aStartOffset);
+ NS_ENSURE_ARG_POINTER(aEndOffset);
+ *aStartOffset = *aEndOffset = 0;
+ aText.Truncate();
+
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (mIntl.IsAccessible()) {
+ Intl()->TextAfterOffset(aOffset, aBoundaryType, aStartOffset, aEndOffset,
+ aText);
+ } else {
+ nsString text;
+ mIntl.AsProxy()->GetTextAfterOffset(aOffset, aBoundaryType, text,
+ aStartOffset, aEndOffset);
+ aText = text;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::GetCharacterAtOffset(int32_t aOffset,
+ char16_t* aCharacter)
+{
+ NS_ENSURE_ARG_POINTER(aCharacter);
+ *aCharacter = L'\0';
+
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (mIntl.IsAccessible()) {
+ *aCharacter = Intl()->CharAt(aOffset);
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ *aCharacter = mIntl.AsProxy()->CharAt(aOffset);
+#endif
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::GetTextAttributes(bool aIncludeDefAttrs,
+ int32_t aOffset,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset,
+ nsIPersistentProperties** aAttributes)
+{
+ NS_ENSURE_ARG_POINTER(aStartOffset);
+ NS_ENSURE_ARG_POINTER(aEndOffset);
+ NS_ENSURE_ARG_POINTER(aAttributes);
+ *aStartOffset = *aEndOffset = 0;
+ *aAttributes = nullptr;
+
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIPersistentProperties> props;
+ if (mIntl.IsAccessible()) {
+ props = Intl()->TextAttributes(aIncludeDefAttrs, aOffset, aStartOffset,
+ aEndOffset);
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ AutoTArray<Attribute, 10> attrs;
+ mIntl.AsProxy()->TextAttributes(aIncludeDefAttrs, aOffset, &attrs,
+ aStartOffset, aEndOffset);
+ uint32_t attrCount = attrs.Length();
+ nsAutoString unused;
+ for (uint32_t i = 0; i < attrCount; i++) {
+ props->SetStringProperty(attrs[i].Name(), attrs[i].Value(), unused);
+ }
+#endif
+ }
+ props.forget(aAttributes);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::GetDefaultTextAttributes(nsIPersistentProperties** aAttributes)
+{
+ NS_ENSURE_ARG_POINTER(aAttributes);
+ *aAttributes = nullptr;
+
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIPersistentProperties> props;
+ if (mIntl.IsAccessible()) {
+ props = Intl()->DefaultTextAttributes();
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ AutoTArray<Attribute, 10> attrs;
+ mIntl.AsProxy()->DefaultTextAttributes(&attrs);
+ uint32_t attrCount = attrs.Length();
+ nsAutoString unused;
+ for (uint32_t i = 0; i < attrCount; i++) {
+ props->SetStringProperty(attrs[i].Name(), attrs[i].Value(), unused);
+ }
+#endif
+ }
+ props.forget(aAttributes);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::GetCharacterExtents(int32_t aOffset,
+ int32_t* aX, int32_t* aY,
+ int32_t* aWidth, int32_t* aHeight,
+ uint32_t aCoordType)
+{
+ NS_ENSURE_ARG_POINTER(aX);
+ NS_ENSURE_ARG_POINTER(aY);
+ NS_ENSURE_ARG_POINTER(aWidth);
+ NS_ENSURE_ARG_POINTER(aHeight);
+ *aX = *aY = *aWidth = *aHeight;
+
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ nsIntRect rect;
+ if (mIntl.IsAccessible()) {
+ rect = Intl()->CharBounds(aOffset, aCoordType);
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ rect = mIntl.AsProxy()->CharBounds(aOffset, aCoordType);
+#endif
+ }
+ *aX = rect.x; *aY = rect.y;
+ *aWidth = rect.width; *aHeight = rect.height;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::GetRangeExtents(int32_t aStartOffset, int32_t aEndOffset,
+ int32_t* aX, int32_t* aY,
+ int32_t* aWidth, int32_t* aHeight,
+ uint32_t aCoordType)
+{
+ NS_ENSURE_ARG_POINTER(aX);
+ NS_ENSURE_ARG_POINTER(aY);
+ NS_ENSURE_ARG_POINTER(aWidth);
+ NS_ENSURE_ARG_POINTER(aHeight);
+ *aX = *aY = *aWidth = *aHeight = 0;
+
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ nsIntRect rect;
+ if (mIntl.IsAccessible()) {
+ rect = Intl()->TextBounds(aStartOffset, aEndOffset, aCoordType);
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ rect = mIntl.AsProxy()->TextBounds(aStartOffset, aEndOffset, aCoordType);
+#endif
+ }
+ *aX = rect.x; *aY = rect.y;
+ *aWidth = rect.width; *aHeight = rect.height;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::GetOffsetAtPoint(int32_t aX, int32_t aY,
+ uint32_t aCoordType, int32_t* aOffset)
+{
+ NS_ENSURE_ARG_POINTER(aOffset);
+ *aOffset = -1;
+
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (mIntl.IsAccessible()) {
+ *aOffset = Intl()->OffsetAtPoint(aX, aY, aCoordType);
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ *aOffset = mIntl.AsProxy()->OffsetAtPoint(aX, aY, aCoordType);
+#endif
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::GetCaretOffset(int32_t* aCaretOffset)
+{
+ NS_ENSURE_ARG_POINTER(aCaretOffset);
+ *aCaretOffset = -1;
+
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (mIntl.IsAccessible()) {
+ *aCaretOffset = Intl()->CaretOffset();
+ } else {
+ *aCaretOffset = mIntl.AsProxy()->CaretOffset();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::SetCaretOffset(int32_t aCaretOffset)
+{
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (mIntl.IsAccessible()) {
+ Intl()->SetCaretOffset(aCaretOffset);
+ } else {
+ mIntl.AsProxy()->SetCaretOffset(aCaretOffset);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::GetSelectionCount(int32_t* aSelectionCount)
+{
+ NS_ENSURE_ARG_POINTER(aSelectionCount);
+ *aSelectionCount = 0;
+
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (mIntl.IsAccessible()) {
+ *aSelectionCount = Intl()->SelectionCount();
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ *aSelectionCount = mIntl.AsProxy()->SelectionCount();
+#endif
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::GetSelectionBounds(int32_t aSelectionNum,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset)
+{
+ NS_ENSURE_ARG_POINTER(aStartOffset);
+ NS_ENSURE_ARG_POINTER(aEndOffset);
+ *aStartOffset = *aEndOffset = 0;
+
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (aSelectionNum < 0)
+ return NS_ERROR_INVALID_ARG;
+
+ if (mIntl.IsAccessible()) {
+ if (aSelectionNum >= Intl()->SelectionCount())
+ return NS_ERROR_INVALID_ARG;
+
+ Intl()->SelectionBoundsAt(aSelectionNum, aStartOffset, aEndOffset);
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ nsString unused;
+ mIntl.AsProxy()->SelectionBoundsAt(aSelectionNum, unused, aStartOffset,
+ aEndOffset);
+#endif
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::SetSelectionBounds(int32_t aSelectionNum,
+ int32_t aStartOffset,
+ int32_t aEndOffset)
+{
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (aSelectionNum < 0)
+ return NS_ERROR_INVALID_ARG;
+
+ if (mIntl.IsAccessible()) {
+ if (!Intl()->SetSelectionBoundsAt(aSelectionNum, aStartOffset,
+ aEndOffset)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ if (!mIntl.AsProxy()->SetSelectionBoundsAt(aSelectionNum, aStartOffset,
+ aEndOffset)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+#endif
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::AddSelection(int32_t aStartOffset, int32_t aEndOffset)
+{
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (mIntl.IsAccessible()) {
+ Intl()->AddToSelection(aStartOffset, aEndOffset);
+ } else {
+ mIntl.AsProxy()->AddToSelection(aStartOffset, aEndOffset);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::RemoveSelection(int32_t aSelectionNum)
+{
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (mIntl.IsAccessible()) {
+ Intl()->RemoveFromSelection(aSelectionNum);
+ } else {
+ mIntl.AsProxy()->RemoveFromSelection(aSelectionNum);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::ScrollSubstringTo(int32_t aStartOffset,
+ int32_t aEndOffset,
+ uint32_t aScrollType)
+{
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (mIntl.IsAccessible()) {
+ Intl()->ScrollSubstringTo(aStartOffset, aEndOffset, aScrollType);
+ } else {
+ mIntl.AsProxy()->ScrollSubstringTo(aStartOffset, aEndOffset, aScrollType);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::ScrollSubstringToPoint(int32_t aStartOffset,
+ int32_t aEndOffset,
+ uint32_t aCoordinateType,
+ int32_t aX, int32_t aY)
+{
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (mIntl.IsAccessible()) {
+ Intl()->ScrollSubstringToPoint(aStartOffset, aEndOffset, aCoordinateType,
+ aX, aY);
+ } else {
+ mIntl.AsProxy()->ScrollSubstringToPoint(aStartOffset, aEndOffset,
+ aCoordinateType, aX, aY);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::GetEnclosingRange(nsIAccessibleTextRange** aRange)
+{
+ NS_ENSURE_ARG_POINTER(aRange);
+ *aRange = nullptr;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ RefPtr<xpcAccessibleTextRange> range = new xpcAccessibleTextRange;
+ Intl()->EnclosingRange(range->mRange);
+ NS_ASSERTION(range->mRange.IsValid(),
+ "Should always have an enclosing range!");
+
+ range.forget(aRange);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::GetSelectionRanges(nsIArray** aRanges)
+{
+ NS_ENSURE_ARG_POINTER(aRanges);
+ *aRanges = nullptr;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMutableArray> xpcRanges =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AutoTArray<TextRange, 1> ranges;
+ Intl()->SelectionRanges(&ranges);
+ uint32_t len = ranges.Length();
+ for (uint32_t idx = 0; idx < len; idx++)
+ xpcRanges->AppendElement(new xpcAccessibleTextRange(Move(ranges[idx])),
+ false);
+
+ xpcRanges.forget(aRanges);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::GetVisibleRanges(nsIArray** aRanges)
+{
+ NS_ENSURE_ARG_POINTER(aRanges);
+ *aRanges = nullptr;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMutableArray> xpcRanges =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<TextRange> ranges;
+ Intl()->VisibleRanges(&ranges);
+ uint32_t len = ranges.Length();
+ for (uint32_t idx = 0; idx < len; idx++)
+ xpcRanges->AppendElement(new xpcAccessibleTextRange(Move(ranges[idx])),
+ false);
+
+ xpcRanges.forget(aRanges);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::GetRangeByChild(nsIAccessible* aChild,
+ nsIAccessibleTextRange** aRange)
+{
+ NS_ENSURE_ARG_POINTER(aRange);
+ *aRange = nullptr;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ Accessible* child = aChild->ToInternalAccessible();
+ if (child) {
+ RefPtr<xpcAccessibleTextRange> range = new xpcAccessibleTextRange;
+ Intl()->RangeByChild(child, range->mRange);
+ if (range->mRange.IsValid())
+ range.forget(aRange);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::GetRangeAtPoint(int32_t aX, int32_t aY,
+ nsIAccessibleTextRange** aRange)
+{
+ NS_ENSURE_ARG_POINTER(aRange);
+ *aRange = nullptr;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ RefPtr<xpcAccessibleTextRange> range = new xpcAccessibleTextRange;
+ Intl()->RangeAtPoint(aX, aY, range->mRange);
+ if (range->mRange.IsValid())
+ range.forget(aRange);
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIAccessibleEditableText
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::SetTextContents(const nsAString& aText)
+{
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (mIntl.IsAccessible()) {
+ Intl()->ReplaceText(aText);
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ nsString text(aText);
+ mIntl.AsProxy()->ReplaceText(text);
+#endif
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::InsertText(const nsAString& aText, int32_t aOffset)
+{
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (mIntl.IsAccessible()) {
+ Intl()->InsertText(aText, aOffset);
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ nsString text(aText);
+ mIntl.AsProxy()->InsertText(text, aOffset);
+#endif
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::CopyText(int32_t aStartOffset, int32_t aEndOffset)
+{
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (mIntl.IsAccessible()) {
+ Intl()->CopyText(aStartOffset, aEndOffset);
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ mIntl.AsProxy()->CopyText(aStartOffset, aEndOffset);
+#endif
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::CutText(int32_t aStartOffset, int32_t aEndOffset)
+{
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (mIntl.IsAccessible()) {
+ Intl()->CutText(aStartOffset, aEndOffset);
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ mIntl.AsProxy()->CutText(aStartOffset, aEndOffset);
+#endif
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::DeleteText(int32_t aStartOffset, int32_t aEndOffset)
+{
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (mIntl.IsAccessible()) {
+ Intl()->DeleteText(aStartOffset, aEndOffset);
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ mIntl.AsProxy()->DeleteText(aStartOffset, aEndOffset);
+#endif
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::PasteText(int32_t aOffset)
+{
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (mIntl.IsAccessible()) {
+ Intl()->PasteText(aOffset);
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ mIntl.AsProxy()->PasteText(aOffset);
+#endif
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIAccessibleHyperText
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::GetLinkCount(int32_t* aLinkCount)
+{
+ NS_ENSURE_ARG_POINTER(aLinkCount);
+ *aLinkCount = 0;
+
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (mIntl.IsAccessible()) {
+ *aLinkCount = Intl()->LinkCount();
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ *aLinkCount = mIntl.AsProxy()->LinkCount();
+#endif
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::GetLinkAt(int32_t aIndex, nsIAccessibleHyperLink** aLink)
+{
+ NS_ENSURE_ARG_POINTER(aLink);
+ *aLink = nullptr;
+
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (mIntl.IsAccessible()) {
+ NS_IF_ADDREF(*aLink = ToXPC(Intl()->LinkAt(aIndex)));
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ NS_IF_ADDREF(*aLink = ToXPC(mIntl.AsProxy()->LinkAt(aIndex)));
+#endif
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::GetLinkIndex(nsIAccessibleHyperLink* aLink,
+ int32_t* aIndex)
+{
+ NS_ENSURE_ARG_POINTER(aLink);
+ NS_ENSURE_ARG_POINTER(aIndex);
+ *aIndex = -1;
+
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIAccessible> xpcLink(do_QueryInterface(aLink));
+ if (Accessible* accLink = xpcLink->ToInternalAccessible()) {
+ *aIndex = Intl()->LinkIndexOf(accLink);
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ xpcAccessibleHyperText* linkHyperText =
+ static_cast<xpcAccessibleHyperText*>(xpcLink.get());
+ ProxyAccessible* proxyLink = linkHyperText->mIntl.AsProxy();
+ if (proxyLink) {
+ *aIndex = mIntl.AsProxy()->LinkIndexOf(proxyLink);
+ }
+#endif
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleHyperText::GetLinkIndexAtOffset(int32_t aOffset,
+ int32_t* aLinkIndex)
+{
+ NS_ENSURE_ARG_POINTER(aLinkIndex);
+ *aLinkIndex = -1; // API says this magic value means 'not found'
+
+ if (mIntl.IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (mIntl.IsAccessible()) {
+ *aLinkIndex = Intl()->LinkIndexAtOffset(aOffset);
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ *aLinkIndex = mIntl.AsProxy()->LinkIndexAtOffset(aOffset);
+#endif
+ }
+ return NS_OK;
+}
diff --git a/accessible/xpcom/xpcAccessibleHyperText.h b/accessible/xpcom/xpcAccessibleHyperText.h
new file mode 100644
index 000000000..fbdac95cf
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibleHyperText.h
@@ -0,0 +1,62 @@
+/* -*- 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_xpcAccessibleHyperText_h_
+#define mozilla_a11y_xpcAccessibleHyperText_h_
+
+#include "nsIAccessibleText.h"
+#include "nsIAccessibleHyperText.h"
+#include "nsIAccessibleEditableText.h"
+
+#include "HyperTextAccessible.h"
+#include "xpcAccessibleGeneric.h"
+
+namespace mozilla {
+namespace a11y {
+
+class xpcAccessibleHyperText : public xpcAccessibleGeneric,
+ public nsIAccessibleText,
+ public nsIAccessibleEditableText,
+ public nsIAccessibleHyperText
+{
+public:
+ explicit xpcAccessibleHyperText(Accessible* aIntl) :
+ xpcAccessibleGeneric(aIntl)
+ {
+ if (aIntl->IsHyperText() && aIntl->AsHyperText()->IsTextRole())
+ mSupportedIfaces |= eText;
+ }
+
+ xpcAccessibleHyperText(ProxyAccessible* aProxy, uint32_t aInterfaces) :
+ xpcAccessibleGeneric(aProxy, aInterfaces) { mSupportedIfaces |= eText; }
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_DECL_NSIACCESSIBLETEXT
+ NS_DECL_NSIACCESSIBLEHYPERTEXT
+ NS_DECL_NSIACCESSIBLEEDITABLETEXT
+
+protected:
+ virtual ~xpcAccessibleHyperText() {}
+
+private:
+ HyperTextAccessible* Intl()
+ {
+ if (Accessible* acc = mIntl.AsAccessible()) {
+ return acc->AsHyperText();
+ }
+
+ return nullptr;
+ }
+
+ xpcAccessibleHyperText(const xpcAccessibleHyperText&) = delete;
+ xpcAccessibleHyperText& operator =(const xpcAccessibleHyperText&) = delete;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_xpcAccessibleHyperText_h_
diff --git a/accessible/xpcom/xpcAccessibleImage.cpp b/accessible/xpcom/xpcAccessibleImage.cpp
new file mode 100644
index 000000000..c0ef31721
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibleImage.cpp
@@ -0,0 +1,55 @@
+/* -*- 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 "xpcAccessibleImage.h"
+
+#include "ImageAccessible.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED(xpcAccessibleImage,
+ xpcAccessibleGeneric,
+ nsIAccessibleImage)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIAccessibleImage
+
+NS_IMETHODIMP
+xpcAccessibleImage::GetImagePosition(uint32_t aCoordType,
+ int32_t* aX, int32_t* aY)
+{
+ NS_ENSURE_ARG_POINTER(aX);
+ *aX = 0;
+ NS_ENSURE_ARG_POINTER(aY);
+ *aY = 0;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ nsIntPoint point = Intl()->Position(aCoordType);
+ *aX = point.x; *aY = point.y;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleImage::GetImageSize(int32_t* aWidth, int32_t* aHeight)
+{
+ NS_ENSURE_ARG_POINTER(aWidth);
+ *aWidth = 0;
+ NS_ENSURE_ARG_POINTER(aHeight);
+ *aHeight = 0;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ nsIntSize size = Intl()->Size();
+ *aWidth = size.width;
+ *aHeight = size.height;
+ return NS_OK;
+}
diff --git a/accessible/xpcom/xpcAccessibleImage.h b/accessible/xpcom/xpcAccessibleImage.h
new file mode 100644
index 000000000..1c0bc805f
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibleImage.h
@@ -0,0 +1,47 @@
+/* -*- 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_xpcAccessibleImage_h_
+#define mozilla_a11y_xpcAccessibleImage_h_
+
+#include "nsIAccessibleImage.h"
+
+#include "xpcAccessibleGeneric.h"
+
+namespace mozilla {
+namespace a11y {
+
+class xpcAccessibleImage : public xpcAccessibleGeneric,
+ public nsIAccessibleImage
+{
+public:
+ explicit xpcAccessibleImage(Accessible* aIntl) :
+ xpcAccessibleGeneric(aIntl) { }
+
+ xpcAccessibleImage(ProxyAccessible* aProxy, uint32_t aInterfaces) :
+ xpcAccessibleGeneric(aProxy, aInterfaces) {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_IMETHOD GetImagePosition(uint32_t aCoordType,
+ int32_t* aX, int32_t* aY) final override;
+ NS_IMETHOD GetImageSize(int32_t* aWidth, int32_t* aHeight) final override;
+
+protected:
+ virtual ~xpcAccessibleImage() {}
+
+private:
+ ImageAccessible* Intl()
+ { return mIntl.IsAccessible() ? mIntl.AsAccessible()->AsImage() : nullptr; }
+
+ xpcAccessibleImage(const xpcAccessibleImage&) = delete;
+ xpcAccessibleImage& operator =(const xpcAccessibleImage&) = delete;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/xpcom/xpcAccessibleSelectable.cpp b/accessible/xpcom/xpcAccessibleSelectable.cpp
new file mode 100644
index 000000000..df63e116c
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibleSelectable.cpp
@@ -0,0 +1,134 @@
+/* -*- 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 "Accessible-inl.h"
+#include "xpcAccessibleDocument.h"
+
+#include "nsIMutableArray.h"
+
+using namespace mozilla::a11y;
+
+NS_IMETHODIMP
+xpcAccessibleSelectable::GetSelectedItems(nsIArray** aSelectedItems)
+{
+ NS_ENSURE_ARG_POINTER(aSelectedItems);
+ *aSelectedItems = nullptr;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+ NS_PRECONDITION(Intl()->IsSelect(), "Called on non selectable widget!");
+
+ AutoTArray<Accessible*, 10> items;
+ Intl()->SelectedItems(&items);
+
+ uint32_t itemCount = items.Length();
+ if (itemCount == 0)
+ return NS_OK;
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMutableArray> xpcItems =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t idx = 0; idx < itemCount; idx++)
+ xpcItems->AppendElement(static_cast<nsIAccessible*>(ToXPC(items[idx])), false);
+
+ NS_ADDREF(*aSelectedItems = xpcItems);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleSelectable::GetSelectedItemCount(uint32_t* aSelectionCount)
+{
+ NS_ENSURE_ARG_POINTER(aSelectionCount);
+ *aSelectionCount = 0;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+ NS_PRECONDITION(Intl()->IsSelect(), "Called on non selectable widget!");
+
+ *aSelectionCount = Intl()->SelectedItemCount();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleSelectable::GetSelectedItemAt(uint32_t aIndex,
+ nsIAccessible** aSelected)
+{
+ NS_ENSURE_ARG_POINTER(aSelected);
+ *aSelected = nullptr;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+ NS_PRECONDITION(Intl()->IsSelect(), "Called on non selectable widget!");
+
+ *aSelected = ToXPC(Intl()->GetSelectedItem(aIndex));
+ if (*aSelected) {
+ NS_ADDREF(*aSelected);
+ return NS_OK;
+ }
+
+ return NS_ERROR_INVALID_ARG;
+}
+
+NS_IMETHODIMP
+xpcAccessibleSelectable::IsItemSelected(uint32_t aIndex, bool* aIsSelected)
+{
+ NS_ENSURE_ARG_POINTER(aIsSelected);
+ *aIsSelected = false;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+ NS_PRECONDITION(Intl()->IsSelect(), "Called on non selectable widget!");
+
+ *aIsSelected = Intl()->IsItemSelected(aIndex);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleSelectable::AddItemToSelection(uint32_t aIndex)
+{
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+ NS_PRECONDITION(Intl()->IsSelect(), "Called on non selectable widget!");
+
+ return Intl()->AddItemToSelection(aIndex) ? NS_OK : NS_ERROR_INVALID_ARG;
+}
+
+NS_IMETHODIMP
+xpcAccessibleSelectable::RemoveItemFromSelection(uint32_t aIndex)
+{
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+ NS_PRECONDITION(Intl()->IsSelect(), "Called on non selectable widget!");
+
+ return Intl()->RemoveItemFromSelection(aIndex) ? NS_OK : NS_ERROR_INVALID_ARG;
+}
+
+NS_IMETHODIMP
+xpcAccessibleSelectable::SelectAll(bool* aIsMultiSelect)
+{
+ NS_ENSURE_ARG_POINTER(aIsMultiSelect);
+ *aIsMultiSelect = false;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+ NS_PRECONDITION(Intl()->IsSelect(), "Called on non selectable widget!");
+
+ *aIsMultiSelect = Intl()->SelectAll();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleSelectable::UnselectAll()
+{
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+ NS_PRECONDITION(Intl()->IsSelect(), "Called on non selectable widget!");
+
+ Intl()->UnselectAll();
+ return NS_OK;
+}
diff --git a/accessible/xpcom/xpcAccessibleSelectable.h b/accessible/xpcom/xpcAccessibleSelectable.h
new file mode 100644
index 000000000..7b2f8a827
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibleSelectable.h
@@ -0,0 +1,54 @@
+/* -*- 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_xpcAccessibleSelectable_h_
+#define mozilla_a11y_xpcAccessibleSelectable_h_
+
+#include "nsIAccessibleSelectable.h"
+
+class nsIAccessible;
+class nsIArray;
+
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+
+/**
+ * XPCOM nsIAccessibleSelectable inteface implementation, used by
+ * xpcAccessibleGeneric class.
+ */
+class xpcAccessibleSelectable : public nsIAccessibleSelectable
+{
+public:
+ // nsIAccessibleSelectable
+ NS_IMETHOD GetSelectedItems(nsIArray** aSelectedItems) final override;
+ NS_IMETHOD GetSelectedItemCount(uint32_t* aSelectedItemCount)
+ final override;
+ NS_IMETHOD GetSelectedItemAt(uint32_t aIndex, nsIAccessible** aItem)
+ final override;
+ NS_IMETHOD IsItemSelected(uint32_t aIndex, bool* aIsSelected)
+ final override;
+ NS_IMETHOD AddItemToSelection(uint32_t aIndex) final override;
+ NS_IMETHOD RemoveItemFromSelection(uint32_t aIndex) final override;
+ NS_IMETHOD SelectAll(bool* aIsMultiSelect) final override;
+ NS_IMETHOD UnselectAll() final override;
+
+protected:
+ xpcAccessibleSelectable() { }
+ virtual ~xpcAccessibleSelectable() {}
+
+private:
+ xpcAccessibleSelectable(const xpcAccessibleSelectable&) = delete;
+ xpcAccessibleSelectable& operator =(const xpcAccessibleSelectable&) = delete;
+
+ Accessible* Intl();
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/xpcom/xpcAccessibleTable.cpp b/accessible/xpcom/xpcAccessibleTable.cpp
new file mode 100644
index 000000000..0787bf73f
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibleTable.cpp
@@ -0,0 +1,493 @@
+/* -*- 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 "xpcAccessibleTable.h"
+
+#include "Accessible.h"
+#include "TableAccessible.h"
+#include "xpcAccessibleDocument.h"
+
+#include "nsIMutableArray.h"
+#include "nsComponentManagerUtils.h"
+
+using namespace mozilla::a11y;
+
+static const uint32_t XPC_TABLE_DEFAULT_SIZE = 40;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED(xpcAccessibleTable,
+ xpcAccessibleGeneric,
+ nsIAccessibleTable)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIAccessibleTable
+
+NS_IMETHODIMP
+xpcAccessibleTable::GetCaption(nsIAccessible** aCaption)
+{
+ NS_ENSURE_ARG_POINTER(aCaption);
+ *aCaption = nullptr;
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ NS_IF_ADDREF(*aCaption = ToXPC(Intl()->Caption()));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::GetColumnCount(int32_t* aColumnCount)
+{
+ NS_ENSURE_ARG_POINTER(aColumnCount);
+ *aColumnCount = 0;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ *aColumnCount = Intl()->ColCount();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::GetRowCount(int32_t* aRowCount)
+{
+ NS_ENSURE_ARG_POINTER(aRowCount);
+ *aRowCount = 0;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ *aRowCount = Intl()->RowCount();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::GetCellAt(int32_t aRowIdx, int32_t aColIdx,
+ nsIAccessible** aCell)
+{
+ NS_ENSURE_ARG_POINTER(aCell);
+ *aCell = nullptr;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ if (aRowIdx < 0 || static_cast<uint32_t>(aRowIdx) >= Intl()->RowCount() ||
+ aColIdx < 0 || static_cast<uint32_t>(aColIdx) >= Intl()->ColCount())
+ return NS_ERROR_INVALID_ARG;
+
+ NS_IF_ADDREF(*aCell = ToXPC(Intl()->CellAt(aRowIdx, aColIdx)));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::GetCellIndexAt(int32_t aRowIdx, int32_t aColIdx,
+ int32_t* aCellIdx)
+{
+ NS_ENSURE_ARG_POINTER(aCellIdx);
+ *aCellIdx = -1;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ if (aRowIdx < 0 || static_cast<uint32_t>(aRowIdx) >= Intl()->RowCount() ||
+ aColIdx < 0 || static_cast<uint32_t>(aColIdx) >= Intl()->ColCount())
+ return NS_ERROR_INVALID_ARG;
+
+ *aCellIdx = Intl()->CellIndexAt(aRowIdx, aColIdx);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::GetColumnExtentAt(int32_t aRowIdx, int32_t aColIdx,
+ int32_t* aColumnExtent)
+{
+ NS_ENSURE_ARG_POINTER(aColumnExtent);
+ *aColumnExtent = -1;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ if (aRowIdx < 0 || static_cast<uint32_t>(aRowIdx) >= Intl()->RowCount() ||
+ aColIdx < 0 || static_cast<uint32_t>(aColIdx) >= Intl()->ColCount())
+ return NS_ERROR_INVALID_ARG;
+
+ *aColumnExtent = Intl()->ColExtentAt(aRowIdx, aColIdx);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::GetRowExtentAt(int32_t aRowIdx, int32_t aColIdx,
+ int32_t* aRowExtent)
+{
+ NS_ENSURE_ARG_POINTER(aRowExtent);
+ *aRowExtent = -1;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ if (aRowIdx < 0 || static_cast<uint32_t>(aRowIdx) >= Intl()->RowCount() ||
+ aColIdx < 0 || static_cast<uint32_t>(aColIdx) >= Intl()->ColCount())
+ return NS_ERROR_INVALID_ARG;
+
+ *aRowExtent = Intl()->RowExtentAt(aRowIdx, aColIdx);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::GetColumnDescription(int32_t aColIdx,
+ nsAString& aDescription)
+{
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ if (aColIdx < 0 || static_cast<uint32_t>(aColIdx) >= Intl()->ColCount())
+ return NS_ERROR_INVALID_ARG;
+
+ nsAutoString description;
+ Intl()->ColDescription(aColIdx, description);
+ aDescription.Assign(description);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::GetRowDescription(int32_t aRowIdx, nsAString& aDescription)
+{
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ if (aRowIdx < 0 || static_cast<uint32_t>(aRowIdx) >= Intl()->ColCount())
+ return NS_ERROR_INVALID_ARG;
+
+ nsAutoString description;
+ Intl()->RowDescription(aRowIdx, description);
+ aDescription.Assign(description);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::IsColumnSelected(int32_t aColIdx, bool* aIsSelected)
+{
+ NS_ENSURE_ARG_POINTER(aIsSelected);
+ *aIsSelected = false;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ if (aColIdx < 0 || static_cast<uint32_t>(aColIdx) >= Intl()->ColCount())
+ return NS_ERROR_INVALID_ARG;
+
+ *aIsSelected = Intl()->IsColSelected(aColIdx);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::IsRowSelected(int32_t aRowIdx, bool* aIsSelected)
+{
+ NS_ENSURE_ARG_POINTER(aIsSelected);
+ *aIsSelected = false;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ if (aRowIdx < 0 || static_cast<uint32_t>(aRowIdx) >= Intl()->RowCount())
+ return NS_ERROR_INVALID_ARG;
+
+ *aIsSelected = Intl()->IsRowSelected(aRowIdx);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::IsCellSelected(int32_t aRowIdx, int32_t aColIdx,
+ bool* aIsSelected)
+{
+ NS_ENSURE_ARG_POINTER(aIsSelected);
+ *aIsSelected = false;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ if (aRowIdx < 0 || static_cast<uint32_t>(aRowIdx) >= Intl()->RowCount() ||
+ aColIdx < 0 || static_cast<uint32_t>(aColIdx) >= Intl()->ColCount())
+ return NS_ERROR_INVALID_ARG;
+
+ *aIsSelected = Intl()->IsCellSelected(aRowIdx, aColIdx);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::GetSelectedCellCount(uint32_t* aSelectedCellCount)
+{
+ NS_ENSURE_ARG_POINTER(aSelectedCellCount);
+ *aSelectedCellCount = 0;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ *aSelectedCellCount = Intl()->SelectedCellCount();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::GetSelectedColumnCount(uint32_t* aSelectedColumnCount)
+{
+ NS_ENSURE_ARG_POINTER(aSelectedColumnCount);
+ *aSelectedColumnCount = 0;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ *aSelectedColumnCount = Intl()->SelectedColCount();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::GetSelectedRowCount(uint32_t* aSelectedRowCount)
+{
+ NS_ENSURE_ARG_POINTER(aSelectedRowCount);
+ *aSelectedRowCount = 0;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ *aSelectedRowCount = Intl()->SelectedRowCount();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::GetSelectedCells(nsIArray** aSelectedCells)
+{
+ NS_ENSURE_ARG_POINTER(aSelectedCells);
+ *aSelectedCells = nullptr;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMutableArray> selCells =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AutoTArray<Accessible*, XPC_TABLE_DEFAULT_SIZE> cellsArray;
+ Intl()->SelectedCells(&cellsArray);
+
+ uint32_t totalCount = cellsArray.Length();
+ for (uint32_t idx = 0; idx < totalCount; idx++) {
+ Accessible* cell = cellsArray.ElementAt(idx);
+ selCells->AppendElement(static_cast<nsIAccessible*>(ToXPC(cell)), false);
+ }
+
+ NS_ADDREF(*aSelectedCells = selCells);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::GetSelectedCellIndices(uint32_t* aCellsArraySize,
+ int32_t** aCellsArray)
+{
+ NS_ENSURE_ARG_POINTER(aCellsArraySize);
+ *aCellsArraySize = 0;
+
+ NS_ENSURE_ARG_POINTER(aCellsArray);
+ *aCellsArray = 0;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ AutoTArray<uint32_t, XPC_TABLE_DEFAULT_SIZE> cellsArray;
+ Intl()->SelectedCellIndices(&cellsArray);
+
+ *aCellsArraySize = cellsArray.Length();
+ *aCellsArray = static_cast<int32_t*>
+ (moz_xmalloc(*aCellsArraySize * sizeof(int32_t)));
+ memcpy(*aCellsArray, cellsArray.Elements(),
+ *aCellsArraySize * sizeof(int32_t));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::GetSelectedColumnIndices(uint32_t* aColsArraySize,
+ int32_t** aColsArray)
+{
+ NS_ENSURE_ARG_POINTER(aColsArraySize);
+ *aColsArraySize = 0;
+
+ NS_ENSURE_ARG_POINTER(aColsArray);
+ *aColsArray = 0;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ AutoTArray<uint32_t, XPC_TABLE_DEFAULT_SIZE> colsArray;
+ Intl()->SelectedColIndices(&colsArray);
+
+ *aColsArraySize = colsArray.Length();
+ *aColsArray = static_cast<int32_t*>
+ (moz_xmalloc(*aColsArraySize * sizeof(int32_t)));
+ memcpy(*aColsArray, colsArray.Elements(),
+ *aColsArraySize * sizeof(int32_t));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::GetSelectedRowIndices(uint32_t* aRowsArraySize,
+ int32_t** aRowsArray)
+{
+ NS_ENSURE_ARG_POINTER(aRowsArraySize);
+ *aRowsArraySize = 0;
+
+ NS_ENSURE_ARG_POINTER(aRowsArray);
+ *aRowsArray = 0;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ AutoTArray<uint32_t, XPC_TABLE_DEFAULT_SIZE> rowsArray;
+ Intl()->SelectedRowIndices(&rowsArray);
+
+ *aRowsArraySize = rowsArray.Length();
+ *aRowsArray = static_cast<int32_t*>
+ (moz_xmalloc(*aRowsArraySize * sizeof(int32_t)));
+ memcpy(*aRowsArray, rowsArray.Elements(),
+ *aRowsArraySize * sizeof(int32_t));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::GetColumnIndexAt(int32_t aCellIdx, int32_t* aColIdx)
+{
+ NS_ENSURE_ARG_POINTER(aColIdx);
+ *aColIdx = -1;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ if (aCellIdx < 0 ||
+ static_cast<uint32_t>(aCellIdx) >= Intl()->RowCount() * Intl()->ColCount())
+ return NS_ERROR_INVALID_ARG;
+
+ *aColIdx = Intl()->ColIndexAt(aCellIdx);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::GetRowIndexAt(int32_t aCellIdx, int32_t* aRowIdx)
+{
+ NS_ENSURE_ARG_POINTER(aRowIdx);
+ *aRowIdx = -1;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ if (aCellIdx < 0 ||
+ static_cast<uint32_t>(aCellIdx) >= Intl()->RowCount() * Intl()->ColCount())
+ return NS_ERROR_INVALID_ARG;
+
+ *aRowIdx = Intl()->RowIndexAt(aCellIdx);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::GetRowAndColumnIndicesAt(int32_t aCellIdx, int32_t* aRowIdx,
+ int32_t* aColIdx)
+{
+ NS_ENSURE_ARG_POINTER(aRowIdx);
+ *aRowIdx = -1;
+ NS_ENSURE_ARG_POINTER(aColIdx);
+ *aColIdx = -1;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ if (aCellIdx < 0 ||
+ static_cast<uint32_t>(aCellIdx) >= Intl()->RowCount() * Intl()->ColCount())
+ return NS_ERROR_INVALID_ARG;
+
+ Intl()->RowAndColIndicesAt(aCellIdx, aRowIdx, aColIdx);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::GetSummary(nsAString& aSummary)
+{
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ nsAutoString summary;
+ Intl()->Summary(summary);
+ aSummary.Assign(summary);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::IsProbablyForLayout(bool* aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ *aResult = Intl()->IsProbablyLayoutTable();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::SelectColumn(int32_t aColIdx)
+{
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ if (aColIdx < 0 || static_cast<uint32_t>(aColIdx) >= Intl()->ColCount())
+ return NS_ERROR_INVALID_ARG;
+
+ Intl()->SelectCol(aColIdx);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::SelectRow(int32_t aRowIdx)
+{
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ if (aRowIdx < 0 || static_cast<uint32_t>(aRowIdx) >= Intl()->RowCount())
+ return NS_ERROR_INVALID_ARG;
+
+ Intl()->SelectRow(aRowIdx);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::UnselectColumn(int32_t aColIdx)
+{
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ if (aColIdx < 0 || static_cast<uint32_t>(aColIdx) >= Intl()->ColCount())
+ return NS_ERROR_INVALID_ARG;
+
+ Intl()->UnselectCol(aColIdx);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTable::UnselectRow(int32_t aRowIdx)
+{
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ if (aRowIdx < 0 || static_cast<uint32_t>(aRowIdx) >= Intl()->RowCount())
+ return NS_ERROR_INVALID_ARG;
+
+ Intl()->UnselectRow(aRowIdx);
+ return NS_OK;
+}
diff --git a/accessible/xpcom/xpcAccessibleTable.h b/accessible/xpcom/xpcAccessibleTable.h
new file mode 100644
index 000000000..36dd77e0a
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibleTable.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++ final; tab-width: 2 final; indent-tabs-mode: nil final; 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_xpcAccessibleTable_h_
+#define mozilla_a11y_xpcAccessibleTable_h_
+
+#include "nsIAccessibleTable.h"
+#include "xpcAccessibleGeneric.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * XPCOM wrapper around TableAccessible class.
+ */
+class xpcAccessibleTable : public xpcAccessibleGeneric,
+ public nsIAccessibleTable
+{
+public:
+ explicit xpcAccessibleTable(Accessible* aIntl) :
+ xpcAccessibleGeneric(aIntl) { }
+
+ xpcAccessibleTable(ProxyAccessible* aProxy, uint32_t aInterfaces) :
+ xpcAccessibleGeneric(aProxy, aInterfaces) {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIAccessibleTable
+ NS_IMETHOD GetCaption(nsIAccessible** aCaption) final override;
+ NS_IMETHOD GetSummary(nsAString& aSummary) final override;
+ NS_IMETHOD GetColumnCount(int32_t* aColumnCount) final override;
+ NS_IMETHOD GetRowCount(int32_t* aRowCount) final override;
+ NS_IMETHOD GetCellAt(int32_t aRowIndex, int32_t aColumnIndex,
+ nsIAccessible** aCell) final override;
+ NS_IMETHOD GetCellIndexAt(int32_t aRowIndex, int32_t aColumnIndex,
+ int32_t* aCellIndex) final override;
+ NS_IMETHOD GetColumnIndexAt(int32_t aCellIndex, int32_t* aColumnIndex)
+ final override;
+ NS_IMETHOD GetRowIndexAt(int32_t aCellIndex, int32_t* aRowIndex)
+ final override;
+ NS_IMETHOD GetRowAndColumnIndicesAt(int32_t aCellIndex, int32_t* aRowIndex,
+ int32_t* aColumnIndex)
+ final override;
+ NS_IMETHOD GetColumnExtentAt(int32_t row, int32_t column,
+ int32_t* aColumnExtent) final override;
+ NS_IMETHOD GetRowExtentAt(int32_t row, int32_t column,
+ int32_t* aRowExtent) final override;
+ NS_IMETHOD GetColumnDescription(int32_t aColIdx, nsAString& aDescription)
+ final override;
+ NS_IMETHOD GetRowDescription(int32_t aRowIdx, nsAString& aDescription)
+ final override;
+ NS_IMETHOD IsColumnSelected(int32_t aColIdx, bool* _retval)
+ final override;
+ NS_IMETHOD IsRowSelected(int32_t aRowIdx, bool* _retval)
+ final override;
+ NS_IMETHOD IsCellSelected(int32_t aRowIdx, int32_t aColIdx, bool* _retval)
+ final override;
+ NS_IMETHOD GetSelectedCellCount(uint32_t* aSelectedCellCount)
+ final override;
+ NS_IMETHOD GetSelectedColumnCount(uint32_t* aSelectedColumnCount)
+ final override;
+ NS_IMETHOD GetSelectedRowCount(uint32_t* aSelectedRowCount)
+ final override;
+ NS_IMETHOD GetSelectedCells(nsIArray** aSelectedCell) final override;
+ NS_IMETHOD GetSelectedCellIndices(uint32_t* aCellsArraySize,
+ int32_t** aCellsArray)
+ final override;
+ NS_IMETHOD GetSelectedColumnIndices(uint32_t* aColsArraySize,
+ int32_t** aColsArray)
+ final override;
+ NS_IMETHOD GetSelectedRowIndices(uint32_t* aRowsArraySize,
+ int32_t** aRowsArray) final override;
+ NS_IMETHOD SelectColumn(int32_t aColIdx) final override;
+ NS_IMETHOD SelectRow(int32_t aRowIdx) final override;
+ NS_IMETHOD UnselectColumn(int32_t aColIdx) final override;
+ NS_IMETHOD UnselectRow(int32_t aRowIdx) final override;
+ NS_IMETHOD IsProbablyForLayout(bool* aIsForLayout) final override;
+
+protected:
+ virtual ~xpcAccessibleTable() {}
+
+private:
+ TableAccessible* Intl()
+ { return mIntl.IsAccessible() ? mIntl.AsAccessible()->AsTable() : nullptr; }
+
+ xpcAccessibleTable(const xpcAccessibleTable&) = delete;
+ xpcAccessibleTable& operator =(const xpcAccessibleTable&) = delete;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_xpcAccessibleTable_h_
diff --git a/accessible/xpcom/xpcAccessibleTableCell.cpp b/accessible/xpcom/xpcAccessibleTableCell.cpp
new file mode 100644
index 000000000..e507a8816
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibleTableCell.cpp
@@ -0,0 +1,161 @@
+/* -*- 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 "xpcAccessibleTableCell.h"
+
+#include "Accessible.h"
+#include "nsIAccessibleTable.h"
+#include "TableAccessible.h"
+#include "TableCellAccessible.h"
+#include "xpcAccessibleDocument.h"
+
+#include "nsComponentManagerUtils.h"
+#include "nsIMutableArray.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED(xpcAccessibleTableCell,
+ xpcAccessibleHyperText,
+ nsIAccessibleTableCell)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIAccessibleTableCell
+
+NS_IMETHODIMP
+xpcAccessibleTableCell::GetTable(nsIAccessibleTable** aTable)
+{
+ NS_ENSURE_ARG_POINTER(aTable);
+ *aTable = nullptr;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ TableAccessible* table = Intl()->Table();
+ if (!table)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIAccessibleTable> xpcTable =
+ do_QueryInterface(static_cast<nsIAccessible*>(ToXPC(table->AsAccessible())));
+ xpcTable.forget(aTable);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTableCell::GetColumnIndex(int32_t* aColIdx)
+{
+ NS_ENSURE_ARG_POINTER(aColIdx);
+ *aColIdx = -1;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ *aColIdx = Intl()->ColIdx();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTableCell::GetRowIndex(int32_t* aRowIdx)
+{
+ NS_ENSURE_ARG_POINTER(aRowIdx);
+ *aRowIdx = -1;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ *aRowIdx = Intl()->RowIdx();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTableCell::GetColumnExtent(int32_t* aExtent)
+{
+ NS_ENSURE_ARG_POINTER(aExtent);
+ *aExtent = -1;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ *aExtent = Intl()->ColExtent();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTableCell::GetRowExtent(int32_t* aExtent)
+{
+ NS_ENSURE_ARG_POINTER(aExtent);
+ *aExtent = -1;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ *aExtent = Intl()->RowExtent();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTableCell::GetColumnHeaderCells(nsIArray** aHeaderCells)
+{
+ NS_ENSURE_ARG_POINTER(aHeaderCells);
+ *aHeaderCells = nullptr;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ AutoTArray<Accessible*, 10> headerCells;
+ Intl()->ColHeaderCells(&headerCells);
+
+ nsCOMPtr<nsIMutableArray> cells = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ NS_ENSURE_TRUE(cells, NS_ERROR_FAILURE);
+
+ for (uint32_t idx = 0; idx < headerCells.Length(); idx++) {
+ cells->AppendElement(static_cast<nsIAccessible*>(ToXPC(headerCells[idx])),
+ false);
+ }
+
+ NS_ADDREF(*aHeaderCells = cells);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTableCell::GetRowHeaderCells(nsIArray** aHeaderCells)
+{
+ NS_ENSURE_ARG_POINTER(aHeaderCells);
+ *aHeaderCells = nullptr;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ AutoTArray<Accessible*, 10> headerCells;
+ Intl()->RowHeaderCells(&headerCells);
+
+ nsCOMPtr<nsIMutableArray> cells = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ NS_ENSURE_TRUE(cells, NS_ERROR_FAILURE);
+
+ for (uint32_t idx = 0; idx < headerCells.Length(); idx++) {
+ cells->AppendElement(static_cast<nsIAccessible*>(ToXPC(headerCells[idx])),
+ false);
+ }
+
+ NS_ADDREF(*aHeaderCells = cells);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTableCell::IsSelected(bool* aSelected)
+{
+ NS_ENSURE_ARG_POINTER(aSelected);
+ *aSelected = false;
+
+ if (!Intl())
+ return NS_ERROR_FAILURE;
+
+ *aSelected = Intl()->Selected();
+ return NS_OK;
+}
diff --git a/accessible/xpcom/xpcAccessibleTableCell.h b/accessible/xpcom/xpcAccessibleTableCell.h
new file mode 100644
index 000000000..9a40f8e1a
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibleTableCell.h
@@ -0,0 +1,62 @@
+/* -*- 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_xpcom_xpcAccessibletableCell_h_
+#define mozilla_a11y_xpcom_xpcAccessibletableCell_h_
+
+#include "nsIAccessibleTable.h"
+
+#include "xpcAccessibleHyperText.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * XPCOM wrapper around TableAccessibleCell class.
+ */
+class xpcAccessibleTableCell : public xpcAccessibleHyperText,
+ public nsIAccessibleTableCell
+{
+public:
+ explicit xpcAccessibleTableCell(Accessible* aIntl) :
+ xpcAccessibleHyperText(aIntl) { }
+
+ xpcAccessibleTableCell(ProxyAccessible* aProxy, uint32_t aInterfaces) :
+ xpcAccessibleHyperText(aProxy, aInterfaces) {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIAccessibleTableCell
+ NS_IMETHOD GetTable(nsIAccessibleTable** aTable) final override;
+ NS_IMETHOD GetColumnIndex(int32_t* aColIdx) final override;
+ NS_IMETHOD GetRowIndex(int32_t* aRowIdx) final override;
+ NS_IMETHOD GetColumnExtent(int32_t* aExtent) final override;
+ NS_IMETHOD GetRowExtent(int32_t* aExtent) final override;
+ NS_IMETHOD GetColumnHeaderCells(nsIArray** aHeaderCells) final override;
+ NS_IMETHOD GetRowHeaderCells(nsIArray** aHeaderCells) final override;
+ NS_IMETHOD IsSelected(bool* aSelected) final override;
+
+protected:
+ virtual ~xpcAccessibleTableCell() {}
+
+private:
+ TableCellAccessible* Intl()
+ {
+ if (Accessible* acc = mIntl.AsAccessible()) {
+ return acc->AsTableCell();
+ }
+
+ return nullptr;
+}
+
+ xpcAccessibleTableCell(const xpcAccessibleTableCell&) = delete;
+ xpcAccessibleTableCell& operator =(const xpcAccessibleTableCell&) = delete;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_xpcom_xpcAccessibletableCell_h_
diff --git a/accessible/xpcom/xpcAccessibleTextRange.cpp b/accessible/xpcom/xpcAccessibleTextRange.cpp
new file mode 100644
index 000000000..07a6ec6d3
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibleTextRange.cpp
@@ -0,0 +1,221 @@
+/* -*- 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 "xpcAccessibleTextRange.h"
+
+#include "TextRange-inl.h"
+#include "xpcAccessibleDocument.h"
+
+#include "nsIMutableArray.h"
+#include "nsComponentManagerUtils.h"
+#include "nsQueryObject.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+// nsISupports and cycle collection
+
+NS_IMPL_CYCLE_COLLECTION(xpcAccessibleTextRange,
+ mRange.mRoot,
+ mRange.mStartContainer,
+ mRange.mEndContainer)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(xpcAccessibleTextRange)
+ NS_INTERFACE_MAP_ENTRY(nsIAccessibleTextRange)
+ NS_INTERFACE_MAP_ENTRY(xpcAccessibleTextRange)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAccessibleTextRange)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(xpcAccessibleTextRange)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(xpcAccessibleTextRange)
+
+// nsIAccessibleTextRange
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::GetStartContainer(nsIAccessibleText** aAnchor)
+{
+ NS_ENSURE_ARG_POINTER(aAnchor);
+ NS_IF_ADDREF(*aAnchor = ToXPCText(mRange.StartContainer()));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::GetStartOffset(int32_t* aOffset)
+{
+ NS_ENSURE_ARG_POINTER(aOffset);
+ *aOffset = mRange.StartOffset();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::GetEndContainer(nsIAccessibleText** aAnchor)
+{
+ NS_ENSURE_ARG_POINTER(aAnchor);
+ NS_IF_ADDREF(*aAnchor = ToXPCText(mRange.EndContainer()));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::GetEndOffset(int32_t* aOffset)
+{
+ NS_ENSURE_ARG_POINTER(aOffset);
+ *aOffset = mRange.EndOffset();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::GetContainer(nsIAccessible** aContainer)
+{
+ NS_ENSURE_ARG_POINTER(aContainer);
+ NS_IF_ADDREF(*aContainer = ToXPC(mRange.Container()));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::GetEmbeddedChildren(nsIArray** aList)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMutableArray> xpcList =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<Accessible*> objects;
+ mRange.EmbeddedChildren(&objects);
+
+ uint32_t len = objects.Length();
+ for (uint32_t idx = 0; idx < len; idx++)
+ xpcList->AppendElement(static_cast<nsIAccessible*>(ToXPC(objects[idx])), false);
+
+ xpcList.forget(aList);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::Compare(nsIAccessibleTextRange* aOtherRange,
+ bool* aResult)
+{
+
+ RefPtr<xpcAccessibleTextRange> xpcRange(do_QueryObject(aOtherRange));
+ if (!xpcRange || !aResult)
+ return NS_ERROR_INVALID_ARG;
+
+ *aResult = (mRange == xpcRange->mRange);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::CompareEndPoints(uint32_t aEndPoint,
+ nsIAccessibleTextRange* aOtherRange,
+ uint32_t aOtherRangeEndPoint,
+ int32_t* aResult)
+{
+ RefPtr<xpcAccessibleTextRange> xpcRange(do_QueryObject(aOtherRange));
+ if (!xpcRange || !aResult)
+ return NS_ERROR_INVALID_ARG;
+
+ TextPoint p = (aEndPoint == EndPoint_Start) ?
+ mRange.StartPoint() : mRange.EndPoint();
+ TextPoint otherPoint = (aOtherRangeEndPoint == EndPoint_Start) ?
+ xpcRange->mRange.StartPoint() : xpcRange->mRange.EndPoint();
+
+ if (p == otherPoint)
+ *aResult = 0;
+ else
+ *aResult = p < otherPoint ? -1 : 1;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::GetText(nsAString& aText)
+{
+ nsAutoString text;
+ mRange.Text(text);
+ aText.Assign(text);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::GetBounds(nsIArray** aRectList)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::Move(uint32_t aUnit, int32_t aCount)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::MoveStart(uint32_t aUnit, int32_t aCount)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::MoveEnd(uint32_t aUnit, int32_t aCount)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::Normalize(uint32_t aUnit)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::Crop(nsIAccessible* aContainer, bool* aSuccess)
+{
+ Accessible* container = aContainer->ToInternalAccessible();
+ NS_ENSURE_TRUE(container, NS_ERROR_INVALID_ARG);
+
+ *aSuccess = mRange.Crop(container);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::FindText(const nsAString& aText, bool aIsBackward,
+ bool aIsIgnoreCase,
+ nsIAccessibleTextRange** aRange)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::FindAttr(uint32_t aAttr, nsIVariant* aVal,
+ bool aIsBackward,
+ nsIAccessibleTextRange** aRange)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::AddToSelection()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::RemoveFromSelection()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::Select()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::ScrollIntoView(uint32_t aHow)
+{
+ return NS_OK;
+}
diff --git a/accessible/xpcom/xpcAccessibleTextRange.h b/accessible/xpcom/xpcAccessibleTextRange.h
new file mode 100644
index 000000000..315707703
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibleTextRange.h
@@ -0,0 +1,85 @@
+/* -*- 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_xpcAccessibleTextRange_h_
+#define mozilla_a11y_xpcAccessibleTextRange_h_
+
+#include "nsIAccessibleTextRange.h"
+#include "TextRange.h"
+
+#include "mozilla/Move.h"
+#include "nsCycleCollectionParticipant.h"
+
+namespace mozilla {
+namespace a11y {
+
+class TextRange;
+
+#define NS_ACCESSIBLETEXTRANGE_IMPL_IID \
+{ /* 133c8bf4-4913-4355-bd50-426bd1d6e1ad */ \
+ 0xb17652d9, \
+ 0x4f54, \
+ 0x4c56, \
+ { 0xbb, 0x62, 0x6d, 0x5b, 0xf1, 0xef, 0x91, 0x0c } \
+}
+
+class xpcAccessibleTextRange final : public nsIAccessibleTextRange
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(xpcAccessibleTextRange)
+
+ NS_IMETHOD GetStartContainer(nsIAccessibleText** aAnchor) final override;
+ NS_IMETHOD GetStartOffset(int32_t* aOffset) final override;
+ NS_IMETHOD GetEndContainer(nsIAccessibleText** aAnchor) final override;
+ NS_IMETHOD GetEndOffset(int32_t* aOffset) final override;
+ NS_IMETHOD GetContainer(nsIAccessible** aContainer) final override;
+ NS_IMETHOD GetEmbeddedChildren(nsIArray** aList) final override;
+ NS_IMETHOD Compare(nsIAccessibleTextRange* aOtherRange, bool* aResult) final override;
+ NS_IMETHOD CompareEndPoints(uint32_t aEndPoint,
+ nsIAccessibleTextRange* aOtherRange,
+ uint32_t aOtherRangeEndPoint,
+ int32_t* aResult) final override;
+ NS_IMETHOD GetText(nsAString& aText) final override;
+ NS_IMETHOD GetBounds(nsIArray** aRectList) final override;
+ NS_IMETHOD Move(uint32_t aUnit, int32_t aCount) final override;
+ NS_IMETHOD MoveStart(uint32_t aUnit, int32_t aCount) final override;
+ NS_IMETHOD MoveEnd(uint32_t aUnit, int32_t aCount) final override;
+ NS_IMETHOD Normalize(uint32_t aUnit) final override;
+ NS_IMETHOD Crop(nsIAccessible* aContainer, bool* aSuccess) final override;
+ NS_IMETHOD FindText(const nsAString& aText, bool aIsBackward, bool aIsIgnoreCase,
+ nsIAccessibleTextRange** aRange) final override;
+ NS_IMETHOD FindAttr(uint32_t aAttr, nsIVariant* aVal, bool aIsBackward,
+ nsIAccessibleTextRange** aRange) final override;
+ NS_IMETHOD AddToSelection() final override;
+ NS_IMETHOD RemoveFromSelection() final override;
+ NS_IMETHOD Select() final override;
+ NS_IMETHOD ScrollIntoView(uint32_t aHow) final override;
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ACCESSIBLETEXTRANGE_IMPL_IID)
+
+private:
+ explicit xpcAccessibleTextRange(TextRange&& aRange) :
+ mRange(Forward<TextRange>(aRange)) {}
+ xpcAccessibleTextRange() {}
+
+ ~xpcAccessibleTextRange() {}
+
+ friend class xpcAccessibleHyperText;
+
+ xpcAccessibleTextRange(const xpcAccessibleTextRange&) = delete;
+ xpcAccessibleTextRange& operator =(const xpcAccessibleTextRange&) = delete;
+
+ TextRange mRange;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(xpcAccessibleTextRange,
+ NS_ACCESSIBLETEXTRANGE_IMPL_IID)
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/xpcom/xpcAccessibleValue.cpp b/accessible/xpcom/xpcAccessibleValue.cpp
new file mode 100644
index 000000000..f3628ff03
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibleValue.cpp
@@ -0,0 +1,133 @@
+/* -*- 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 "xpcAccessibleGeneric.h"
+#include "Accessible.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+NS_IMETHODIMP
+xpcAccessibleValue::GetMaximumValue(double* aValue)
+{
+ NS_ENSURE_ARG_POINTER(aValue);
+ *aValue = 0;
+
+ if (Intl().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (Intl().IsAccessible() && Intl().AsAccessible()->IsDefunct())
+ return NS_ERROR_FAILURE;
+
+ double value;
+ if (Intl().IsAccessible()) {
+ value = Intl().AsAccessible()->MaxValue();
+ } else {
+ value = Intl().AsProxy()->MaxValue();
+ }
+
+ if (!IsNaN(value))
+ *aValue = value;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleValue::GetMinimumValue(double* aValue)
+{
+ NS_ENSURE_ARG_POINTER(aValue);
+ *aValue = 0;
+
+ if (Intl().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (Intl().IsAccessible() && Intl().AsAccessible()->IsDefunct())
+ return NS_ERROR_FAILURE;
+
+ double value;
+ if (Intl().IsAccessible()) {
+ value = Intl().AsAccessible()->MinValue();
+ } else {
+ value = Intl().AsProxy()->MinValue();
+ }
+
+ if (!IsNaN(value))
+ *aValue = value;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleValue::GetCurrentValue(double* aValue)
+{
+ NS_ENSURE_ARG_POINTER(aValue);
+ *aValue = 0;
+
+ if (Intl().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (Intl().IsAccessible() && Intl().AsAccessible()->IsDefunct())
+ return NS_ERROR_FAILURE;
+
+ double value;
+ if (Intl().IsAccessible()) {
+ value = Intl().AsAccessible()->CurValue();
+ } else {
+ value = Intl().AsProxy()->CurValue();
+ }
+
+ if (!IsNaN(value))
+ *aValue = value;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleValue::SetCurrentValue(double aValue)
+{
+ if (Intl().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (Intl().IsAccessible() && Intl().AsAccessible()->IsDefunct())
+ return NS_ERROR_FAILURE;
+
+ if (Intl().IsAccessible()) {
+ Intl().AsAccessible()->SetCurValue(aValue);
+ } else {
+ Intl().AsProxy()->SetCurValue(aValue);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleValue::GetMinimumIncrement(double* aValue)
+{
+ NS_ENSURE_ARG_POINTER(aValue);
+ *aValue = 0;
+
+ if (Intl().IsNull())
+ return NS_ERROR_FAILURE;
+
+ if (Intl().IsAccessible() && Intl().AsAccessible()->IsDefunct())
+ return NS_ERROR_FAILURE;
+
+ double value;
+ if (Intl().IsAccessible()) {
+ value = Intl().AsAccessible()->Step();
+ } else {
+#if defined(XP_WIN)
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ value = Intl().AsProxy()->Step();
+#endif
+ }
+
+ if (!IsNaN(value))
+ *aValue = value;
+
+ return NS_OK;
+}
diff --git a/accessible/xpcom/xpcAccessibleValue.h b/accessible/xpcom/xpcAccessibleValue.h
new file mode 100644
index 000000000..740b512b8
--- /dev/null
+++ b/accessible/xpcom/xpcAccessibleValue.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 mozilla_a11y_xpcAccessibleValue_h_
+#define mozilla_a11y_xpcAccessibleValue_h_
+
+#include "nsIAccessibleValue.h"
+
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+
+/**
+ * XPCOM nsIAccessibleValue interface implementation, used by
+ * xpcAccessibleGeneric class.
+ */
+class xpcAccessibleValue : public nsIAccessibleValue
+{
+public:
+ NS_IMETHOD GetMaximumValue(double* aValue) final override;
+ NS_IMETHOD GetMinimumValue(double* aValue) final override;
+ NS_IMETHOD GetCurrentValue(double* aValue) final override;
+ NS_IMETHOD SetCurrentValue(double aValue) final override;
+ NS_IMETHOD GetMinimumIncrement(double* aMinIncrement) final override;
+
+protected:
+ xpcAccessibleValue() { }
+ virtual ~xpcAccessibleValue() {}
+
+private:
+ AccessibleOrProxy Intl();
+
+ xpcAccessibleValue(const xpcAccessibleValue&) = delete;
+ xpcAccessibleValue& operator =(const xpcAccessibleValue&) = delete;
+};
+
+} // namespace a11y
+} // namespace mozilla
+#endif
diff --git a/accessible/xul/XULAlertAccessible.cpp b/accessible/xul/XULAlertAccessible.cpp
new file mode 100644
index 000000000..4b1b5bd8d
--- /dev/null
+++ b/accessible/xul/XULAlertAccessible.cpp
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "XULAlertAccessible.h"
+
+#include "Accessible-inl.h"
+#include "Role.h"
+#include "States.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// XULAlertAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULAlertAccessible::
+ XULAlertAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+ mGenericTypes |= eAlert;
+}
+
+XULAlertAccessible::~XULAlertAccessible()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(XULAlertAccessible, Accessible)
+
+role
+XULAlertAccessible::NativeRole()
+{
+ return roles::ALERT;
+}
+
+uint64_t
+XULAlertAccessible::NativeState()
+{
+ return Accessible::NativeState() | states::ALERT;
+}
+
+ENameValueFlag
+XULAlertAccessible::Name(nsString& aName)
+{
+ // Screen readers need to read contents of alert, not the accessible name.
+ // If we have both some screen readers will read the alert twice.
+ aName.Truncate();
+ return eNameOK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Widgets
+
+bool
+XULAlertAccessible::IsWidget() const
+{
+ return true;
+}
+
+Accessible*
+XULAlertAccessible::ContainerWidget() const
+{
+ // If a part of colorpicker widget.
+ if (mParent && mParent->IsMenuButton())
+ return mParent;
+ return nullptr;
+}
diff --git a/accessible/xul/XULAlertAccessible.h b/accessible/xul/XULAlertAccessible.h
new file mode 100644
index 000000000..62bf4cb46
--- /dev/null
+++ b/accessible/xul/XULAlertAccessible.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_XULAlertAccessible_h__
+#define mozilla_a11y_XULAlertAccessible_h__
+
+#include "AccessibleWrap.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Accessible for supporting XUL alerts.
+ */
+
+class XULAlertAccessible : public AccessibleWrap
+{
+public:
+ XULAlertAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual mozilla::a11y::ENameValueFlag Name(nsString& aName) override;
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+
+ // Widgets
+ virtual bool IsWidget() const override;
+ virtual Accessible* ContainerWidget() const override;
+
+protected:
+ ~XULAlertAccessible();
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/xul/XULColorPickerAccessible.cpp b/accessible/xul/XULColorPickerAccessible.cpp
new file mode 100644
index 000000000..1bc507e9a
--- /dev/null
+++ b/accessible/xul/XULColorPickerAccessible.cpp
@@ -0,0 +1,143 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "XULColorPickerAccessible.h"
+
+#include "Accessible-inl.h"
+#include "nsAccUtils.h"
+#include "nsCoreUtils.h"
+#include "DocAccessible.h"
+#include "Role.h"
+#include "States.h"
+
+#include "nsIDOMElement.h"
+#include "nsMenuPopupFrame.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// XULColorPickerTileAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULColorPickerTileAccessible::
+ XULColorPickerTileAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULColorPickerTileAccessible: Accessible
+
+void
+XULColorPickerTileAccessible::Value(nsString& aValue)
+{
+ aValue.Truncate();
+
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::color, aValue);
+}
+
+role
+XULColorPickerTileAccessible::NativeRole()
+{
+ return roles::PUSHBUTTON;
+}
+
+uint64_t
+XULColorPickerTileAccessible::NativeState()
+{
+ uint64_t state = AccessibleWrap::NativeState();
+ if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::selected))
+ state |= states::SELECTED;
+
+ return state;
+}
+
+uint64_t
+XULColorPickerTileAccessible::NativeInteractiveState() const
+{
+ return NativelyUnavailable() ?
+ states::UNAVAILABLE : states::FOCUSABLE | states::SELECTABLE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULColorPickerTileAccessible: Widgets
+
+Accessible*
+XULColorPickerTileAccessible::ContainerWidget() const
+{
+ Accessible* parent = Parent();
+ if (parent) {
+ Accessible* grandParent = parent->Parent();
+ if (grandParent && grandParent->IsMenuButton())
+ return grandParent;
+ }
+ return nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULColorPickerAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULColorPickerAccessible::
+ XULColorPickerAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ XULColorPickerTileAccessible(aContent, aDoc)
+{
+ mGenericTypes |= eMenuButton;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULColorPickerAccessible: Accessible
+
+uint64_t
+XULColorPickerAccessible::NativeState()
+{
+ uint64_t state = AccessibleWrap::NativeState();
+ return state | states::HASPOPUP;
+}
+
+role
+XULColorPickerAccessible::NativeRole()
+{
+ return roles::BUTTONDROPDOWNGRID;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULColorPickerAccessible: Widgets
+
+bool
+XULColorPickerAccessible::IsWidget() const
+{
+ return true;
+}
+
+bool
+XULColorPickerAccessible::IsActiveWidget() const
+{
+ return FocusMgr()->HasDOMFocus(mContent);
+}
+
+bool
+XULColorPickerAccessible::AreItemsOperable() const
+{
+ Accessible* menuPopup = mChildren.SafeElementAt(0, nullptr);
+ if (menuPopup) {
+ nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(menuPopup->GetFrame());
+ return menuPopupFrame && menuPopupFrame->IsOpen();
+ }
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULColorPickerAccessible: Accessible
+
+bool
+XULColorPickerAccessible::IsAcceptableChild(nsIContent* aEl) const
+{
+ nsAutoString role;
+ nsCoreUtils::XBLBindingRole(aEl, role);
+ return role.EqualsLiteral("xul:panel") &&
+ aEl->AttrValueIs(kNameSpaceID_None, nsGkAtoms::noautofocus,
+ nsGkAtoms::_true, eCaseMatters);
+}
diff --git a/accessible/xul/XULColorPickerAccessible.h b/accessible/xul/XULColorPickerAccessible.h
new file mode 100644
index 000000000..fba4111ca
--- /dev/null
+++ b/accessible/xul/XULColorPickerAccessible.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_XULColorPickerAccessible_h__
+#define mozilla_a11y_XULColorPickerAccessible_h__
+
+#include "AccessibleWrap.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Used for color button in colorpicker palette.
+ */
+class XULColorPickerTileAccessible : public AccessibleWrap
+{
+public:
+ XULColorPickerTileAccessible(nsIContent* aContent,
+ DocAccessible* aDoc);
+
+ // Accessible
+ virtual void Value(nsString& aValue) override;
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+ virtual uint64_t NativeInteractiveState() const override;
+
+ // Widgets
+ virtual Accessible* ContainerWidget() const override;
+};
+
+
+/**
+ * Used for colorpicker button (xul:colorpicker@type="button").
+ */
+class XULColorPickerAccessible : public XULColorPickerTileAccessible
+{
+public:
+ XULColorPickerAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+
+ // Widgets
+ virtual bool IsWidget() const override;
+ virtual bool IsActiveWidget() const override;
+ virtual bool AreItemsOperable() const override;
+
+ virtual bool IsAcceptableChild(nsIContent* aEl) const override;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/xul/XULComboboxAccessible.cpp b/accessible/xul/XULComboboxAccessible.cpp
new file mode 100644
index 000000000..1bca6d58e
--- /dev/null
+++ b/accessible/xul/XULComboboxAccessible.cpp
@@ -0,0 +1,194 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "XULComboboxAccessible.h"
+
+#include "Accessible-inl.h"
+#include "nsAccessibilityService.h"
+#include "DocAccessible.h"
+#include "nsCoreUtils.h"
+#include "Role.h"
+#include "States.h"
+
+#include "nsIAutoCompleteInput.h"
+#include "nsIDOMXULMenuListElement.h"
+#include "nsIDOMXULSelectCntrlItemEl.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// XULComboboxAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULComboboxAccessible::
+ XULComboboxAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::autocomplete, eIgnoreCase))
+ mGenericTypes |= eAutoComplete;
+ else
+ mGenericTypes |= eCombobox;
+
+ // Both the XUL <textbox type="autocomplete"> and <menulist editable="true">
+ // widgets use XULComboboxAccessible. We need to walk the anonymous children
+ // for these so that the entry field is a child. Otherwise no XBL children.
+ if (!mContent->NodeInfo()->Equals(nsGkAtoms::textbox, kNameSpaceID_XUL) &&
+ !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::editable,
+ nsGkAtoms::_true, eIgnoreCase)) {
+ mStateFlags |= eNoXBLKids;
+ }
+}
+
+role
+XULComboboxAccessible::NativeRole()
+{
+ return IsAutoComplete() ? roles::AUTOCOMPLETE : roles::COMBOBOX;
+}
+
+uint64_t
+XULComboboxAccessible::NativeState()
+{
+ // As a nsComboboxAccessible we can have the following states:
+ // STATE_FOCUSED
+ // STATE_FOCUSABLE
+ // STATE_HASPOPUP
+ // STATE_EXPANDED
+ // STATE_COLLAPSED
+
+ // Get focus status from base class
+ uint64_t state = Accessible::NativeState();
+
+ nsCOMPtr<nsIDOMXULMenuListElement> menuList(do_QueryInterface(mContent));
+ if (menuList) {
+ bool isOpen = false;
+ menuList->GetOpen(&isOpen);
+ if (isOpen)
+ state |= states::EXPANDED;
+ else
+ state |= states::COLLAPSED;
+ }
+
+ return state | states::HASPOPUP;
+}
+
+void
+XULComboboxAccessible::Description(nsString& aDescription)
+{
+ aDescription.Truncate();
+ // Use description of currently focused option
+ nsCOMPtr<nsIDOMXULMenuListElement> menuListElm(do_QueryInterface(mContent));
+ if (!menuListElm)
+ return;
+
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> focusedOptionItem;
+ menuListElm->GetSelectedItem(getter_AddRefs(focusedOptionItem));
+ nsCOMPtr<nsIContent> focusedOptionContent =
+ do_QueryInterface(focusedOptionItem);
+ if (focusedOptionContent && mDoc) {
+ Accessible* focusedOptionAcc = mDoc->GetAccessible(focusedOptionContent);
+ if (focusedOptionAcc)
+ focusedOptionAcc->Description(aDescription);
+ }
+}
+
+void
+XULComboboxAccessible::Value(nsString& aValue)
+{
+ aValue.Truncate();
+
+ // The value is the option or text shown entered in the combobox.
+ nsCOMPtr<nsIDOMXULMenuListElement> menuList(do_QueryInterface(mContent));
+ if (menuList)
+ menuList->GetLabel(aValue);
+}
+
+uint8_t
+XULComboboxAccessible::ActionCount()
+{
+ // Just one action (click).
+ return 1;
+}
+
+bool
+XULComboboxAccessible::DoAction(uint8_t aIndex)
+{
+ if (aIndex != XULComboboxAccessible::eAction_Click)
+ return false;
+
+ // Programmaticaly toggle the combo box.
+ nsCOMPtr<nsIDOMXULMenuListElement> menuList(do_QueryInterface(mContent));
+ if (!menuList)
+ return false;
+
+ bool isDroppedDown = false;
+ menuList->GetOpen(&isDroppedDown);
+ menuList->SetOpen(!isDroppedDown);
+ return true;
+}
+
+void
+XULComboboxAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ aName.Truncate();
+ if (aIndex != XULComboboxAccessible::eAction_Click)
+ return;
+
+ nsCOMPtr<nsIDOMXULMenuListElement> menuList(do_QueryInterface(mContent));
+ if (!menuList)
+ return;
+
+ bool isDroppedDown = false;
+ menuList->GetOpen(&isDroppedDown);
+ if (isDroppedDown)
+ aName.AssignLiteral("close");
+ else
+ aName.AssignLiteral("open");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Widgets
+
+bool
+XULComboboxAccessible::IsActiveWidget() const
+{
+ if (IsAutoComplete() ||
+ mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::editable,
+ nsGkAtoms::_true, eIgnoreCase)) {
+ int32_t childCount = mChildren.Length();
+ for (int32_t idx = 0; idx < childCount; idx++) {
+ Accessible* child = mChildren[idx];
+ if (child->Role() == roles::ENTRY)
+ return FocusMgr()->HasDOMFocus(child->GetContent());
+ }
+ return false;
+ }
+
+ return FocusMgr()->HasDOMFocus(mContent);
+}
+
+bool
+XULComboboxAccessible::AreItemsOperable() const
+{
+ if (IsAutoComplete()) {
+ nsCOMPtr<nsIAutoCompleteInput> autoCompleteInputElm =
+ do_QueryInterface(mContent);
+ if (autoCompleteInputElm) {
+ bool isOpen = false;
+ autoCompleteInputElm->GetPopupOpen(&isOpen);
+ return isOpen;
+ }
+ return false;
+ }
+
+ nsCOMPtr<nsIDOMXULMenuListElement> menuListElm = do_QueryInterface(mContent);
+ if (menuListElm) {
+ bool isOpen = false;
+ menuListElm->GetOpen(&isOpen);
+ return isOpen;
+ }
+
+ return false;
+}
diff --git a/accessible/xul/XULComboboxAccessible.h b/accessible/xul/XULComboboxAccessible.h
new file mode 100644
index 000000000..679f479b5
--- /dev/null
+++ b/accessible/xul/XULComboboxAccessible.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_XULComboboxAccessible_h__
+#define mozilla_a11y_XULComboboxAccessible_h__
+
+#include "XULMenuAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Used for XUL comboboxes like xul:menulist and autocomplete textbox.
+ */
+class XULComboboxAccessible : public AccessibleWrap
+{
+public:
+ enum { eAction_Click = 0 };
+
+ XULComboboxAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual void Description(nsString& aDescription) override;
+ virtual void Value(nsString& aValue) override;
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+
+ // Widgets
+ virtual bool IsActiveWidget() const override;
+ virtual bool AreItemsOperable() const override;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/xul/XULElementAccessibles.cpp b/accessible/xul/XULElementAccessibles.cpp
new file mode 100644
index 000000000..b50ddb4ed
--- /dev/null
+++ b/accessible/xul/XULElementAccessibles.cpp
@@ -0,0 +1,288 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "XULElementAccessibles.h"
+
+#include "Accessible-inl.h"
+#include "BaseAccessibles.h"
+#include "DocAccessible-inl.h"
+#include "nsAccUtils.h"
+#include "nsCoreUtils.h"
+#include "nsTextEquivUtils.h"
+#include "Relation.h"
+#include "Role.h"
+#include "States.h"
+#include "TextUpdater.h"
+
+#ifdef A11Y_LOG
+#include "Logging.h"
+#endif
+
+#include "nsIDOMXULDescriptionElement.h"
+#include "nsNameSpaceManager.h"
+#include "nsNetUtil.h"
+#include "nsString.h"
+#include "nsTextBoxFrame.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// XULLabelAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULLabelAccessible::
+ XULLabelAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HyperTextAccessibleWrap(aContent, aDoc)
+{
+ mType = eXULLabelType;
+
+ // If @value attribute is given then it's rendered instead text content. In
+ // this case we need to create a text leaf accessible to make @value attribute
+ // accessible.
+ // XXX: text interface doesn't let you get the text by words.
+ nsTextBoxFrame* textBoxFrame = do_QueryFrame(mContent->GetPrimaryFrame());
+ if (textBoxFrame) {
+ mValueTextLeaf = new XULLabelTextLeafAccessible(mContent, mDoc);
+ mDoc->BindToDocument(mValueTextLeaf, nullptr);
+
+ nsAutoString text;
+ textBoxFrame->GetCroppedTitle(text);
+ mValueTextLeaf->SetText(text);
+ AppendChild(mValueTextLeaf);
+ }
+}
+
+void
+XULLabelAccessible::Shutdown()
+{
+ mValueTextLeaf = nullptr;
+ HyperTextAccessibleWrap::Shutdown();
+}
+
+ENameValueFlag
+XULLabelAccessible::NativeName(nsString& aName)
+{
+ // if the value attr doesn't exist, the screen reader must get the accessible text
+ // from the accessible text interface or from the children
+ if (mValueTextLeaf)
+ return mValueTextLeaf->Name(aName);
+
+ return Accessible::NativeName(aName);
+}
+
+role
+XULLabelAccessible::NativeRole()
+{
+ return roles::LABEL;
+}
+
+uint64_t
+XULLabelAccessible::NativeState()
+{
+ // Labels and description have read only state
+ // They are not focusable or selectable
+ return HyperTextAccessibleWrap::NativeState() | states::READONLY;
+}
+
+Relation
+XULLabelAccessible::RelationByType(RelationType aType)
+{
+ Relation rel = HyperTextAccessibleWrap::RelationByType(aType);
+ if (aType == RelationType::LABEL_FOR) {
+ // Caption is the label for groupbox
+ nsIContent* parent = mContent->GetFlattenedTreeParent();
+ if (parent && parent->IsXULElement(nsGkAtoms::caption)) {
+ Accessible* parent = Parent();
+ if (parent && parent->Role() == roles::GROUPING)
+ rel.AppendTarget(parent);
+ }
+ }
+
+ return rel;
+}
+
+void
+XULLabelAccessible::UpdateLabelValue(const nsString& aValue)
+{
+#ifdef A11Y_LOG
+ if (logging::IsEnabled(logging::eText)) {
+ logging::MsgBegin("TEXT", "text may be changed (xul:label @value update)");
+ logging::Node("container", mContent);
+ logging::MsgEntry("old text '%s'",
+ NS_ConvertUTF16toUTF8(mValueTextLeaf->Text()).get());
+ logging::MsgEntry("new text: '%s'",
+ NS_ConvertUTF16toUTF8(aValue).get());
+ logging::MsgEnd();
+ }
+#endif
+
+ TextUpdater::Run(mDoc, mValueTextLeaf, aValue);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// XULLabelTextLeafAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+role
+XULLabelTextLeafAccessible::NativeRole()
+{
+ return roles::TEXT_LEAF;
+}
+
+uint64_t
+XULLabelTextLeafAccessible::NativeState()
+{
+ return TextLeafAccessibleWrap::NativeState() | states::READONLY;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTooltipAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULTooltipAccessible::
+ XULTooltipAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ LeafAccessible(aContent, aDoc)
+{
+}
+
+uint64_t
+XULTooltipAccessible::NativeState()
+{
+ return LeafAccessible::NativeState() | states::READONLY;
+}
+
+role
+XULTooltipAccessible::NativeRole()
+{
+ return roles::TOOLTIP;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// XULLinkAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULLinkAccessible::
+ XULLinkAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ XULLabelAccessible(aContent, aDoc)
+{
+}
+
+XULLinkAccessible::~XULLinkAccessible()
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULLinkAccessible: Accessible
+
+void
+XULLinkAccessible::Value(nsString& aValue)
+{
+ aValue.Truncate();
+
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::href, aValue);
+}
+
+ENameValueFlag
+XULLinkAccessible::NativeName(nsString& aName)
+{
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, aName);
+ if (!aName.IsEmpty())
+ return eNameOK;
+
+ nsTextEquivUtils::GetNameFromSubtree(this, aName);
+ return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
+}
+
+role
+XULLinkAccessible::NativeRole()
+{
+ return roles::LINK;
+}
+
+
+uint64_t
+XULLinkAccessible::NativeLinkState() const
+{
+ return states::LINKED;
+}
+
+uint8_t
+XULLinkAccessible::ActionCount()
+{
+ return 1;
+}
+
+void
+XULLinkAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ aName.Truncate();
+
+ if (aIndex == eAction_Jump)
+ aName.AssignLiteral("jump");
+}
+
+bool
+XULLinkAccessible::DoAction(uint8_t aIndex)
+{
+ if (aIndex != eAction_Jump)
+ return false;
+
+ DoCommand();
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULLinkAccessible: HyperLinkAccessible
+
+bool
+XULLinkAccessible::IsLink()
+{
+ // Expose HyperLinkAccessible unconditionally.
+ return true;
+}
+
+uint32_t
+XULLinkAccessible::StartOffset()
+{
+ // If XUL link accessible is not contained by hypertext accessible then
+ // start offset matches index in parent because the parent doesn't contains
+ // a text.
+ // XXX: accessible parent of XUL link accessible should be a hypertext
+ // accessible.
+ if (Accessible::IsLink())
+ return Accessible::StartOffset();
+ return IndexInParent();
+}
+
+uint32_t
+XULLinkAccessible::EndOffset()
+{
+ if (Accessible::IsLink())
+ return Accessible::EndOffset();
+ return IndexInParent() + 1;
+}
+
+already_AddRefed<nsIURI>
+XULLinkAccessible::AnchorURIAt(uint32_t aAnchorIndex)
+{
+ if (aAnchorIndex != 0)
+ return nullptr;
+
+ nsAutoString href;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
+
+ nsCOMPtr<nsIURI> baseURI = mContent->GetBaseURI();
+ nsIDocument* document = mContent->OwnerDoc();
+
+ nsCOMPtr<nsIURI> anchorURI;
+ NS_NewURI(getter_AddRefs(anchorURI), href,
+ document->GetDocumentCharacterSet().get(),
+ baseURI);
+
+ return anchorURI.forget();
+}
diff --git a/accessible/xul/XULElementAccessibles.h b/accessible/xul/XULElementAccessibles.h
new file mode 100644
index 000000000..57a5ea03b
--- /dev/null
+++ b/accessible/xul/XULElementAccessibles.h
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_XULElementAccessibles_h__
+#define mozilla_a11y_XULElementAccessibles_h__
+
+#include "HyperTextAccessibleWrap.h"
+#include "TextLeafAccessibleWrap.h"
+
+namespace mozilla {
+namespace a11y {
+
+class XULLabelTextLeafAccessible;
+
+/**
+ * Used for XUL description and label elements.
+ */
+class XULLabelAccessible : public HyperTextAccessibleWrap
+{
+public:
+ XULLabelAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual void Shutdown() override;
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+ virtual Relation RelationByType(RelationType aType) override;
+
+ void UpdateLabelValue(const nsString& aValue);
+
+protected:
+ // Accessible
+ virtual ENameValueFlag NativeName(nsString& aName) override;
+
+private:
+ RefPtr<XULLabelTextLeafAccessible> mValueTextLeaf;
+};
+
+inline XULLabelAccessible*
+Accessible::AsXULLabel()
+{
+ return IsXULLabel() ? static_cast<XULLabelAccessible*>(this) : nullptr;
+}
+
+
+/**
+ * Used to implement text interface on XUL label accessible in case when text
+ * is provided by @value attribute (no underlying text frame).
+ */
+class XULLabelTextLeafAccessible final : public TextLeafAccessibleWrap
+{
+public:
+ XULLabelTextLeafAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ TextLeafAccessibleWrap(aContent, aDoc)
+ { mStateFlags |= eSharedNode; }
+
+ virtual ~XULLabelTextLeafAccessible() { }
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+};
+
+
+/**
+ * Used for XUL tooltip element.
+ */
+class XULTooltipAccessible : public LeafAccessible
+{
+
+public:
+ XULTooltipAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+};
+
+class XULLinkAccessible : public XULLabelAccessible
+{
+
+public:
+ XULLinkAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual void Value(nsString& aValue) override;
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeLinkState() const override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+
+ // HyperLinkAccessible
+ virtual bool IsLink() override;
+ virtual uint32_t StartOffset() override;
+ virtual uint32_t EndOffset() override;
+ virtual already_AddRefed<nsIURI> AnchorURIAt(uint32_t aAnchorIndex) override;
+
+protected:
+ virtual ~XULLinkAccessible();
+
+ // Accessible
+ virtual ENameValueFlag NativeName(nsString& aName) override;
+
+ enum { eAction_Jump = 0 };
+
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/xul/XULFormControlAccessible.cpp b/accessible/xul/XULFormControlAccessible.cpp
new file mode 100644
index 000000000..95947f37c
--- /dev/null
+++ b/accessible/xul/XULFormControlAccessible.cpp
@@ -0,0 +1,633 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "XULFormControlAccessible.h"
+
+#include "Accessible-inl.h"
+#include "HTMLFormControlAccessible.h"
+#include "nsAccUtils.h"
+#include "nsCoreUtils.h"
+#include "DocAccessible.h"
+#include "nsIAccessibleRelation.h"
+#include "Relation.h"
+#include "Role.h"
+#include "States.h"
+#include "TreeWalker.h"
+#include "XULMenuAccessible.h"
+
+#include "nsIDOMNSEditableElement.h"
+#include "nsIDOMXULButtonElement.h"
+#include "nsIDOMXULCheckboxElement.h"
+#include "nsIDOMXULMenuListElement.h"
+#include "nsIDOMXULSelectCntrlItemEl.h"
+#include "nsIDOMXULTextboxElement.h"
+#include "nsIEditor.h"
+#include "nsIFrame.h"
+#include "nsITextControlFrame.h"
+#include "nsMenuPopupFrame.h"
+#include "nsNameSpaceManager.h"
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// XULButtonAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULButtonAccessible::
+ XULButtonAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+ if (ContainsMenu()) {
+ mGenericTypes |= eMenuButton;
+ } else {
+ mGenericTypes |= eButton;
+ }
+}
+
+XULButtonAccessible::~XULButtonAccessible()
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULButtonAccessible: nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED0(XULButtonAccessible, Accessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// XULButtonAccessible: nsIAccessible
+
+uint8_t
+XULButtonAccessible::ActionCount()
+{
+ return 1;
+}
+
+void
+XULButtonAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ if (aIndex == eAction_Click)
+ aName.AssignLiteral("press");
+}
+
+bool
+XULButtonAccessible::DoAction(uint8_t aIndex)
+{
+ if (aIndex != 0)
+ return false;
+
+ DoCommand();
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULButtonAccessible: Accessible
+
+role
+XULButtonAccessible::NativeRole()
+{
+ return roles::PUSHBUTTON;
+}
+
+uint64_t
+XULButtonAccessible::NativeState()
+{
+ // Possible states: focused, focusable, unavailable(disabled).
+
+ // get focus and disable status from base class
+ uint64_t state = Accessible::NativeState();
+
+ // Buttons can be checked -- they simply appear pressed in rather than checked
+ nsCOMPtr<nsIDOMXULButtonElement> xulButtonElement(do_QueryInterface(mContent));
+ if (xulButtonElement) {
+ nsAutoString type;
+ xulButtonElement->GetType(type);
+ if (type.EqualsLiteral("checkbox") || type.EqualsLiteral("radio")) {
+ state |= states::CHECKABLE;
+ bool checked = false;
+ int32_t checkState = 0;
+ xulButtonElement->GetChecked(&checked);
+ if (checked) {
+ state |= states::PRESSED;
+ xulButtonElement->GetCheckState(&checkState);
+ if (checkState == nsIDOMXULButtonElement::CHECKSTATE_MIXED) {
+ state |= states::MIXED;
+ }
+ }
+ }
+ }
+
+ if (ContainsMenu())
+ state |= states::HASPOPUP;
+
+ if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::_default))
+ state |= states::DEFAULT;
+
+ return state;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULButtonAccessible: Widgets
+
+bool
+XULButtonAccessible::IsWidget() const
+{
+ return true;
+}
+
+bool
+XULButtonAccessible::IsActiveWidget() const
+{
+ return FocusMgr()->HasDOMFocus(mContent);
+}
+
+bool
+XULButtonAccessible::AreItemsOperable() const
+{
+ if (IsMenuButton()) {
+ Accessible* menuPopup = mChildren.SafeElementAt(0, nullptr);
+ if (menuPopup) {
+ nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(menuPopup->GetFrame());
+ return menuPopupFrame->IsOpen();
+ }
+ }
+ return false; // no items
+}
+
+Accessible*
+XULButtonAccessible::ContainerWidget() const
+{
+ if (IsMenuButton() && mParent && mParent->IsAutoComplete())
+ return mParent;
+ return nullptr;
+}
+
+bool
+XULButtonAccessible::IsAcceptableChild(nsIContent* aEl) const
+{
+ // In general XUL button has not accessible children. Nevertheless menu
+ // buttons can have button (@type="menu-button") and popup accessibles
+ // (@type="menu-button", @type="menu" or columnpicker.
+
+ // XXX: no children until the button is menu button. Probably it's not
+ // totally correct but in general AT wants to have leaf buttons.
+ nsAutoString role;
+ nsCoreUtils::XBLBindingRole(aEl, role);
+
+ // Get an accessible for menupopup or panel elements.
+ if (role.EqualsLiteral("xul:menupopup")) {
+ return true;
+ }
+
+ // Button type="menu-button" contains a real button. Get an accessible
+ // for it. Ignore dropmarker button which is placed as a last child.
+ if ((!role.EqualsLiteral("xul:button") &&
+ !role.EqualsLiteral("xul:toolbarbutton")) ||
+ aEl->IsXULElement(nsGkAtoms::dropMarker)) {
+ return false;
+ }
+
+ return mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::menuButton, eCaseMatters);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULButtonAccessible protected
+
+bool
+XULButtonAccessible::ContainsMenu() const
+{
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::menu, &nsGkAtoms::menuButton, nullptr};
+
+ return mContent->FindAttrValueIn(kNameSpaceID_None,
+ nsGkAtoms::type,
+ strings, eCaseMatters) >= 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULDropmarkerAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULDropmarkerAccessible::
+ XULDropmarkerAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ LeafAccessible(aContent, aDoc)
+{
+}
+
+uint8_t
+XULDropmarkerAccessible::ActionCount()
+{
+ return 1;
+}
+
+bool
+XULDropmarkerAccessible::DropmarkerOpen(bool aToggleOpen) const
+{
+ bool isOpen = false;
+
+ nsIContent* parent = mContent->GetFlattenedTreeParent();
+
+ while (parent) {
+ nsCOMPtr<nsIDOMXULButtonElement> parentButtonElement =
+ do_QueryInterface(parent);
+ if (parentButtonElement) {
+ parentButtonElement->GetOpen(&isOpen);
+ if (aToggleOpen)
+ parentButtonElement->SetOpen(!isOpen);
+ return isOpen;
+ }
+
+ nsCOMPtr<nsIDOMXULMenuListElement> parentMenuListElement =
+ do_QueryInterface(parent);
+ if (parentMenuListElement) {
+ parentMenuListElement->GetOpen(&isOpen);
+ if (aToggleOpen)
+ parentMenuListElement->SetOpen(!isOpen);
+ return isOpen;
+ }
+ parent = parent->GetFlattenedTreeParent();
+ }
+
+ return isOpen;
+}
+
+void
+XULDropmarkerAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ aName.Truncate();
+ if (aIndex == eAction_Click) {
+ if (DropmarkerOpen(false))
+ aName.AssignLiteral("close");
+ else
+ aName.AssignLiteral("open");
+ }
+}
+
+bool
+XULDropmarkerAccessible::DoAction(uint8_t index)
+{
+ if (index == eAction_Click) {
+ DropmarkerOpen(true); // Reverse the open attribute
+ return true;
+ }
+ return false;
+}
+
+role
+XULDropmarkerAccessible::NativeRole()
+{
+ return roles::PUSHBUTTON;
+}
+
+uint64_t
+XULDropmarkerAccessible::NativeState()
+{
+ return DropmarkerOpen(false) ? states::PRESSED : 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULCheckboxAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULCheckboxAccessible::
+ XULCheckboxAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ LeafAccessible(aContent, aDoc)
+{
+}
+
+role
+XULCheckboxAccessible::NativeRole()
+{
+ return roles::CHECKBUTTON;
+}
+
+uint8_t
+XULCheckboxAccessible::ActionCount()
+{
+ return 1;
+}
+
+void
+XULCheckboxAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ if (aIndex == eAction_Click) {
+ if (NativeState() & states::CHECKED)
+ aName.AssignLiteral("uncheck");
+ else
+ aName.AssignLiteral("check");
+ }
+}
+
+bool
+XULCheckboxAccessible::DoAction(uint8_t aIndex)
+{
+ if (aIndex != eAction_Click)
+ return false;
+
+ DoCommand();
+ return true;
+}
+
+uint64_t
+XULCheckboxAccessible::NativeState()
+{
+ // Possible states: focused, focusable, unavailable(disabled), checked
+ // Get focus and disable status from base class
+ uint64_t state = LeafAccessible::NativeState();
+
+ state |= states::CHECKABLE;
+
+ // Determine Checked state
+ nsCOMPtr<nsIDOMXULCheckboxElement> xulCheckboxElement =
+ do_QueryInterface(mContent);
+ if (xulCheckboxElement) {
+ bool checked = false;
+ xulCheckboxElement->GetChecked(&checked);
+ if (checked) {
+ state |= states::CHECKED;
+ int32_t checkState = 0;
+ xulCheckboxElement->GetCheckState(&checkState);
+ if (checkState == nsIDOMXULCheckboxElement::CHECKSTATE_MIXED)
+ state |= states::MIXED;
+ }
+ }
+
+ return state;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULGroupboxAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULGroupboxAccessible::
+ XULGroupboxAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+}
+
+role
+XULGroupboxAccessible::NativeRole()
+{
+ return roles::GROUPING;
+}
+
+ENameValueFlag
+XULGroupboxAccessible::NativeName(nsString& aName)
+{
+ // XXX: we use the first related accessible only.
+ Accessible* label =
+ RelationByType(RelationType::LABELLED_BY).Next();
+ if (label)
+ return label->Name(aName);
+
+ return eNameOK;
+}
+
+Relation
+XULGroupboxAccessible::RelationByType(RelationType aType)
+{
+ Relation rel = AccessibleWrap::RelationByType(aType);
+ if (aType != RelationType::LABELLED_BY)
+ return rel;
+
+ // The label for xul:groupbox is generated from xul:label that is
+ // inside the anonymous content of the xul:caption.
+ // The xul:label has an accessible object but the xul:caption does not
+ uint32_t childCount = ChildCount();
+ for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
+ Accessible* childAcc = GetChildAt(childIdx);
+ if (childAcc->Role() == roles::LABEL) {
+ // Ensure that it's our label
+ Relation reverseRel = childAcc->RelationByType(RelationType::LABEL_FOR);
+ Accessible* testGroupbox = nullptr;
+ while ((testGroupbox = reverseRel.Next()))
+ if (testGroupbox == this) {
+ // The <label> points back to this groupbox
+ rel.AppendTarget(childAcc);
+ }
+ }
+ }
+
+ return rel;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULRadioButtonAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULRadioButtonAccessible::
+ XULRadioButtonAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ RadioButtonAccessible(aContent, aDoc)
+{
+}
+
+uint64_t
+XULRadioButtonAccessible::NativeState()
+{
+ uint64_t state = LeafAccessible::NativeState();
+ state |= states::CHECKABLE;
+
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> radioButton =
+ do_QueryInterface(mContent);
+ if (radioButton) {
+ bool selected = false; // Radio buttons can be selected
+ radioButton->GetSelected(&selected);
+ if (selected) {
+ state |= states::CHECKED;
+ }
+ }
+
+ return state;
+}
+
+uint64_t
+XULRadioButtonAccessible::NativeInteractiveState() const
+{
+ return NativelyUnavailable() ? states::UNAVAILABLE : states::FOCUSABLE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULRadioButtonAccessible: Widgets
+
+Accessible*
+XULRadioButtonAccessible::ContainerWidget() const
+{
+ return mParent;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// XULRadioGroupAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * XUL Radio Group
+ * The Radio Group proxies for the Radio Buttons themselves. The Group gets
+ * focus whereas the Buttons do not. So we only have an accessible object for
+ * this for the purpose of getting the proper RadioButton. Need this here to
+ * avoid circular reference problems when navigating the accessible tree and
+ * for getting to the radiobuttons.
+ */
+
+XULRadioGroupAccessible::
+ XULRadioGroupAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ XULSelectControlAccessible(aContent, aDoc)
+{
+}
+
+role
+XULRadioGroupAccessible::NativeRole()
+{
+ return roles::RADIO_GROUP;
+}
+
+uint64_t
+XULRadioGroupAccessible::NativeInteractiveState() const
+{
+ // The radio group is not focusable. Sometimes the focus controller will
+ // report that it is focused. That means that the actual selected radio button
+ // should be considered focused.
+ return NativelyUnavailable() ? states::UNAVAILABLE : 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULRadioGroupAccessible: Widgets
+
+bool
+XULRadioGroupAccessible::IsWidget() const
+{
+ return true;
+}
+
+bool
+XULRadioGroupAccessible::IsActiveWidget() const
+{
+ return FocusMgr()->HasDOMFocus(mContent);
+}
+
+bool
+XULRadioGroupAccessible::AreItemsOperable() const
+{
+ return true;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// XULStatusBarAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULStatusBarAccessible::
+ XULStatusBarAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+}
+
+role
+XULStatusBarAccessible::NativeRole()
+{
+ return roles::STATUSBAR;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// XULToolbarButtonAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULToolbarButtonAccessible::
+ XULToolbarButtonAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ XULButtonAccessible(aContent, aDoc)
+{
+}
+
+void
+XULToolbarButtonAccessible::GetPositionAndSizeInternal(int32_t* aPosInSet,
+ int32_t* aSetSize)
+{
+ int32_t setSize = 0;
+ int32_t posInSet = 0;
+
+ Accessible* parent = Parent();
+ if (!parent)
+ return;
+
+ uint32_t childCount = parent->ChildCount();
+ for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
+ Accessible* child = parent->GetChildAt(childIdx);
+ if (IsSeparator(child)) { // end of a group of buttons
+ if (posInSet)
+ break; // we've found our group, so we're done
+
+ setSize = 0; // not our group, so start a new group
+
+ } else {
+ setSize++; // another button in the group
+
+ if (child == this)
+ posInSet = setSize; // we've found our button
+ }
+ }
+
+ *aPosInSet = posInSet;
+ *aSetSize = setSize;
+}
+
+bool
+XULToolbarButtonAccessible::IsSeparator(Accessible* aAccessible)
+{
+ nsIContent* content = aAccessible->GetContent();
+ return content && content->IsAnyOfXULElements(nsGkAtoms::toolbarseparator,
+ nsGkAtoms::toolbarspacer,
+ nsGkAtoms::toolbarspring);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// XULToolbarAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULToolbarAccessible::
+ XULToolbarAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+}
+
+role
+XULToolbarAccessible::NativeRole()
+{
+ return roles::TOOLBAR;
+}
+
+ENameValueFlag
+XULToolbarAccessible::NativeName(nsString& aName)
+{
+ if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::toolbarname, aName))
+ aName.CompressWhitespace();
+
+ return eNameOK;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// XULToolbarAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULToolbarSeparatorAccessible::
+ XULToolbarSeparatorAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ LeafAccessible(aContent, aDoc)
+{
+}
+
+role
+XULToolbarSeparatorAccessible::NativeRole()
+{
+ return roles::SEPARATOR;
+}
+
+uint64_t
+XULToolbarSeparatorAccessible::NativeState()
+{
+ return 0;
+}
diff --git a/accessible/xul/XULFormControlAccessible.h b/accessible/xul/XULFormControlAccessible.h
new file mode 100644
index 000000000..4f11668c9
--- /dev/null
+++ b/accessible/xul/XULFormControlAccessible.h
@@ -0,0 +1,218 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_XULFormControlAccessible_H_
+#define MOZILLA_A11Y_XULFormControlAccessible_H_
+
+// NOTE: alphabetically ordered
+#include "AccessibleWrap.h"
+#include "FormControlAccessible.h"
+#include "HyperTextAccessibleWrap.h"
+#include "XULSelectControlAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Used for XUL progressmeter element.
+ */
+typedef ProgressMeterAccessible<100> XULProgressMeterAccessible;
+
+/**
+ * Used for XUL button.
+ *
+ * @note Don't inherit from LeafAccessible - it doesn't allow children
+ * and a button can have a dropmarker child.
+ */
+class XULButtonAccessible : public AccessibleWrap
+{
+public:
+ enum { eAction_Click = 0 };
+ XULButtonAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+
+ // Widgets
+ virtual bool IsWidget() const override;
+ virtual bool IsActiveWidget() const override;
+ virtual bool AreItemsOperable() const override;
+ virtual Accessible* ContainerWidget() const override;
+
+ virtual bool IsAcceptableChild(nsIContent* aEl) const override;
+
+protected:
+ virtual ~XULButtonAccessible();
+
+ // XULButtonAccessible
+ bool ContainsMenu() const;
+};
+
+
+/**
+ * Used for XUL checkbox element.
+ */
+class XULCheckboxAccessible : public LeafAccessible
+{
+public:
+ enum { eAction_Click = 0 };
+ XULCheckboxAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+};
+
+/**
+ * Used for XUL dropmarker element.
+ */
+class XULDropmarkerAccessible : public LeafAccessible
+{
+public:
+ enum { eAction_Click = 0 };
+ XULDropmarkerAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+
+private:
+ bool DropmarkerOpen(bool aToggleOpen) const;
+};
+
+/**
+ * Used for XUL groupbox element.
+ */
+class XULGroupboxAccessible final : public AccessibleWrap
+{
+public:
+ XULGroupboxAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual Relation RelationByType(RelationType aType) override;
+
+protected:
+ // Accessible
+ virtual ENameValueFlag NativeName(nsString& aName) override;
+};
+
+/**
+ * Used for XUL radio element (radio button).
+ */
+class XULRadioButtonAccessible : public RadioButtonAccessible
+{
+
+public:
+ XULRadioButtonAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual uint64_t NativeState() override;
+ virtual uint64_t NativeInteractiveState() const override;
+
+ // Widgets
+ virtual Accessible* ContainerWidget() const override;
+};
+
+/**
+ * Used for XUL radiogroup element.
+ */
+class XULRadioGroupAccessible : public XULSelectControlAccessible
+{
+public:
+ XULRadioGroupAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual uint64_t NativeInteractiveState() const override;
+
+ // Widgets
+ virtual bool IsWidget() const override;
+ virtual bool IsActiveWidget() const override;
+ virtual bool AreItemsOperable() const override;
+};
+
+/**
+ * Used for XUL statusbar element.
+ */
+class XULStatusBarAccessible : public AccessibleWrap
+{
+public:
+ XULStatusBarAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual mozilla::a11y::role NativeRole() override;
+};
+
+/**
+ * Used for XUL toolbarbutton element.
+ */
+class XULToolbarButtonAccessible : public XULButtonAccessible
+{
+public:
+ XULToolbarButtonAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual void GetPositionAndSizeInternal(int32_t* aPosInSet,
+ int32_t* aSetSize) override;
+
+ // nsXULToolbarButtonAccessible
+ static bool IsSeparator(Accessible* aAccessible);
+};
+
+/**
+ * Used for XUL toolbar element.
+ */
+class XULToolbarAccessible : public AccessibleWrap
+{
+public:
+ XULToolbarAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual mozilla::a11y::role NativeRole() override;
+
+protected:
+ // Accessible
+ virtual ENameValueFlag NativeName(nsString& aName) override;
+};
+
+/**
+ * Used for XUL toolbarseparator element.
+ */
+class XULToolbarSeparatorAccessible : public LeafAccessible
+{
+public:
+ XULToolbarSeparatorAccessible(nsIContent* aContent,
+ DocAccessible* aDoc);
+
+ // Accessible
+ virtual mozilla::a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/xul/XULListboxAccessible.cpp b/accessible/xul/XULListboxAccessible.cpp
new file mode 100644
index 000000000..52c3ddf0f
--- /dev/null
+++ b/accessible/xul/XULListboxAccessible.cpp
@@ -0,0 +1,828 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "XULListboxAccessible.h"
+
+#include "Accessible-inl.h"
+#include "nsAccessibilityService.h"
+#include "nsAccUtils.h"
+#include "DocAccessible.h"
+#include "Role.h"
+#include "States.h"
+
+#include "nsComponentManagerUtils.h"
+#include "nsIAutoCompleteInput.h"
+#include "nsIAutoCompletePopup.h"
+#include "nsIDOMXULMenuListElement.h"
+#include "nsIDOMXULMultSelectCntrlEl.h"
+#include "nsIDOMNodeList.h"
+#include "nsIDOMXULPopupElement.h"
+#include "nsIDOMXULSelectCntrlItemEl.h"
+#include "nsIMutableArray.h"
+#include "nsIPersistentProperties2.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// XULColumAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULColumAccessible::
+ XULColumAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+}
+
+role
+XULColumAccessible::NativeRole()
+{
+ return roles::LIST;
+}
+
+uint64_t
+XULColumAccessible::NativeState()
+{
+ return states::READONLY;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// XULColumnItemAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULColumnItemAccessible::
+ XULColumnItemAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ LeafAccessible(aContent, aDoc)
+{
+}
+
+role
+XULColumnItemAccessible::NativeRole()
+{
+ return roles::COLUMNHEADER;
+}
+
+uint64_t
+XULColumnItemAccessible::NativeState()
+{
+ return states::READONLY;
+}
+
+uint8_t
+XULColumnItemAccessible::ActionCount()
+{
+ return 1;
+}
+
+void
+XULColumnItemAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ if (aIndex == eAction_Click)
+ aName.AssignLiteral("click");
+}
+
+bool
+XULColumnItemAccessible::DoAction(uint8_t aIndex)
+{
+ if (aIndex != eAction_Click)
+ return false;
+
+ DoCommand();
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULListboxAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULListboxAccessible::
+ XULListboxAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ XULSelectControlAccessible(aContent, aDoc)
+{
+ nsIContent* parentContent = mContent->GetFlattenedTreeParent();
+ if (parentContent) {
+ nsCOMPtr<nsIAutoCompletePopup> autoCompletePopupElm =
+ do_QueryInterface(parentContent);
+ if (autoCompletePopupElm)
+ mGenericTypes |= eAutoCompletePopup;
+ }
+
+ if (IsMulticolumn())
+ mGenericTypes |= eTable;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULListboxAccessible: Accessible
+
+uint64_t
+XULListboxAccessible::NativeState()
+{
+ // As a XULListboxAccessible we can have the following states:
+ // FOCUSED, READONLY, FOCUSABLE
+
+ // Get focus status from base class
+ uint64_t states = Accessible::NativeState();
+
+ // see if we are multiple select if so set ourselves as such
+
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::seltype,
+ nsGkAtoms::multiple, eCaseMatters)) {
+ states |= states::MULTISELECTABLE | states::EXTSELECTABLE;
+ }
+
+ return states;
+}
+
+/**
+ * Our value is the label of our ( first ) selected child.
+ */
+void
+XULListboxAccessible::Value(nsString& aValue)
+{
+ aValue.Truncate();
+
+ nsCOMPtr<nsIDOMXULSelectControlElement> select(do_QueryInterface(mContent));
+ if (select) {
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> selectedItem;
+ select->GetSelectedItem(getter_AddRefs(selectedItem));
+ if (selectedItem)
+ selectedItem->GetLabel(aValue);
+ }
+}
+
+role
+XULListboxAccessible::NativeRole()
+{
+ // A richlistbox is used with the new autocomplete URL bar, and has a parent
+ // popup <panel>.
+ nsCOMPtr<nsIDOMXULPopupElement> xulPopup =
+ do_QueryInterface(mContent->GetParent());
+ if (xulPopup)
+ return roles::COMBOBOX_LIST;
+
+ return IsMulticolumn() ? roles::TABLE : roles::LISTBOX;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULListboxAccessible: Table
+
+uint32_t
+XULListboxAccessible::ColCount()
+{
+ nsIContent* headContent = nullptr;
+ for (nsIContent* childContent = mContent->GetFirstChild(); childContent;
+ childContent = childContent->GetNextSibling()) {
+ if (childContent->NodeInfo()->Equals(nsGkAtoms::listcols,
+ kNameSpaceID_XUL)) {
+ headContent = childContent;
+ }
+ }
+ if (!headContent)
+ return 0;
+
+ uint32_t columnCount = 0;
+ for (nsIContent* childContent = headContent->GetFirstChild(); childContent;
+ childContent = childContent->GetNextSibling()) {
+ if (childContent->NodeInfo()->Equals(nsGkAtoms::listcol,
+ kNameSpaceID_XUL)) {
+ columnCount++;
+ }
+ }
+
+ return columnCount;
+}
+
+uint32_t
+XULListboxAccessible::RowCount()
+{
+ nsCOMPtr<nsIDOMXULSelectControlElement> element(do_QueryInterface(mContent));
+
+ uint32_t itemCount = 0;
+ if(element)
+ element->GetItemCount(&itemCount);
+
+ return itemCount;
+}
+
+Accessible*
+XULListboxAccessible::CellAt(uint32_t aRowIndex, uint32_t aColumnIndex)
+{
+ nsCOMPtr<nsIDOMXULSelectControlElement> control =
+ do_QueryInterface(mContent);
+ NS_ENSURE_TRUE(control, nullptr);
+
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> item;
+ control->GetItemAtIndex(aRowIndex, getter_AddRefs(item));
+ if (!item)
+ return nullptr;
+
+ nsCOMPtr<nsIContent> itemContent(do_QueryInterface(item));
+ if (!itemContent)
+ return nullptr;
+
+ Accessible* row = mDoc->GetAccessible(itemContent);
+ NS_ENSURE_TRUE(row, nullptr);
+
+ return row->GetChildAt(aColumnIndex);
+}
+
+bool
+XULListboxAccessible::IsColSelected(uint32_t aColIdx)
+{
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> control =
+ do_QueryInterface(mContent);
+ NS_ASSERTION(control,
+ "Doesn't implement nsIDOMXULMultiSelectControlElement.");
+
+ int32_t selectedrowCount = 0;
+ nsresult rv = control->GetSelectedCount(&selectedrowCount);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return selectedrowCount == static_cast<int32_t>(RowCount());
+}
+
+bool
+XULListboxAccessible::IsRowSelected(uint32_t aRowIdx)
+{
+ nsCOMPtr<nsIDOMXULSelectControlElement> control =
+ do_QueryInterface(mContent);
+ NS_ASSERTION(control,
+ "Doesn't implement nsIDOMXULSelectControlElement.");
+
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> item;
+ nsresult rv = control->GetItemAtIndex(aRowIdx, getter_AddRefs(item));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool isSelected = false;
+ item->GetSelected(&isSelected);
+ return isSelected;
+}
+
+bool
+XULListboxAccessible::IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx)
+{
+ return IsRowSelected(aRowIdx);
+}
+
+uint32_t
+XULListboxAccessible::SelectedCellCount()
+{
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> control =
+ do_QueryInterface(mContent);
+ NS_ASSERTION(control,
+ "Doesn't implement nsIDOMXULMultiSelectControlElement.");
+
+ nsCOMPtr<nsIDOMNodeList> selectedItems;
+ control->GetSelectedItems(getter_AddRefs(selectedItems));
+ if (!selectedItems)
+ return 0;
+
+ uint32_t selectedItemsCount = 0;
+ nsresult rv = selectedItems->GetLength(&selectedItemsCount);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ return selectedItemsCount * ColCount();
+}
+
+uint32_t
+XULListboxAccessible::SelectedColCount()
+{
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> control =
+ do_QueryInterface(mContent);
+ NS_ASSERTION(control,
+ "Doesn't implement nsIDOMXULMultiSelectControlElement.");
+
+ int32_t selectedRowCount = 0;
+ nsresult rv = control->GetSelectedCount(&selectedRowCount);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ return selectedRowCount > 0 &&
+ selectedRowCount == static_cast<int32_t>(RowCount()) ? ColCount() : 0;
+}
+
+uint32_t
+XULListboxAccessible::SelectedRowCount()
+{
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> control =
+ do_QueryInterface(mContent);
+ NS_ASSERTION(control,
+ "Doesn't implement nsIDOMXULMultiSelectControlElement.");
+
+ int32_t selectedRowCount = 0;
+ nsresult rv = control->GetSelectedCount(&selectedRowCount);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ return selectedRowCount >= 0 ? selectedRowCount : 0;
+}
+
+void
+XULListboxAccessible::SelectedCells(nsTArray<Accessible*>* aCells)
+{
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> control =
+ do_QueryInterface(mContent);
+ NS_ASSERTION(control,
+ "Doesn't implement nsIDOMXULMultiSelectControlElement.");
+
+ nsCOMPtr<nsIDOMNodeList> selectedItems;
+ control->GetSelectedItems(getter_AddRefs(selectedItems));
+ if (!selectedItems)
+ return;
+
+ uint32_t selectedItemsCount = 0;
+ DebugOnly<nsresult> rv = selectedItems->GetLength(&selectedItemsCount);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "GetLength() Shouldn't fail!");
+
+ for (uint32_t index = 0; index < selectedItemsCount; index++) {
+ nsCOMPtr<nsIDOMNode> itemNode;
+ selectedItems->Item(index, getter_AddRefs(itemNode));
+ nsCOMPtr<nsIContent> itemContent(do_QueryInterface(itemNode));
+ Accessible* item = mDoc->GetAccessible(itemContent);
+
+ if (item) {
+ uint32_t cellCount = item->ChildCount();
+ for (uint32_t cellIdx = 0; cellIdx < cellCount; cellIdx++) {
+ Accessible* cell = mChildren[cellIdx];
+ if (cell->Role() == roles::CELL)
+ aCells->AppendElement(cell);
+ }
+ }
+ }
+}
+
+void
+XULListboxAccessible::SelectedCellIndices(nsTArray<uint32_t>* aCells)
+{
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> control =
+ do_QueryInterface(mContent);
+ NS_ASSERTION(control,
+ "Doesn't implement nsIDOMXULMultiSelectControlElement.");
+
+ nsCOMPtr<nsIDOMNodeList> selectedItems;
+ control->GetSelectedItems(getter_AddRefs(selectedItems));
+ if (!selectedItems)
+ return;
+
+ uint32_t selectedItemsCount = 0;
+ DebugOnly<nsresult> rv = selectedItems->GetLength(&selectedItemsCount);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "GetLength() Shouldn't fail!");
+
+ uint32_t colCount = ColCount();
+ aCells->SetCapacity(selectedItemsCount * colCount);
+ aCells->AppendElements(selectedItemsCount * colCount);
+
+ for (uint32_t selItemsIdx = 0, cellsIdx = 0;
+ selItemsIdx < selectedItemsCount; selItemsIdx++) {
+
+ nsCOMPtr<nsIDOMNode> itemNode;
+ selectedItems->Item(selItemsIdx, getter_AddRefs(itemNode));
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> item =
+ do_QueryInterface(itemNode);
+
+ if (item) {
+ int32_t itemIdx = -1;
+ control->GetIndexOfItem(item, &itemIdx);
+ if (itemIdx >= 0)
+ for (uint32_t colIdx = 0; colIdx < colCount; colIdx++, cellsIdx++)
+ aCells->ElementAt(cellsIdx) = itemIdx * colCount + colIdx;
+ }
+ }
+}
+
+void
+XULListboxAccessible::SelectedColIndices(nsTArray<uint32_t>* aCols)
+{
+ uint32_t selColCount = SelectedColCount();
+ aCols->SetCapacity(selColCount);
+
+ for (uint32_t colIdx = 0; colIdx < selColCount; colIdx++)
+ aCols->AppendElement(colIdx);
+}
+
+void
+XULListboxAccessible::SelectedRowIndices(nsTArray<uint32_t>* aRows)
+{
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> control =
+ do_QueryInterface(mContent);
+ NS_ASSERTION(control,
+ "Doesn't implement nsIDOMXULMultiSelectControlElement.");
+
+ nsCOMPtr<nsIDOMNodeList> selectedItems;
+ control->GetSelectedItems(getter_AddRefs(selectedItems));
+ if (!selectedItems)
+ return;
+
+ uint32_t rowCount = 0;
+ DebugOnly<nsresult> rv = selectedItems->GetLength(&rowCount);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "GetLength() Shouldn't fail!");
+
+ if (!rowCount)
+ return;
+
+ aRows->SetCapacity(rowCount);
+ aRows->AppendElements(rowCount);
+
+ for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ nsCOMPtr<nsIDOMNode> itemNode;
+ selectedItems->Item(rowIdx, getter_AddRefs(itemNode));
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> item =
+ do_QueryInterface(itemNode);
+
+ if (item) {
+ int32_t itemIdx = -1;
+ control->GetIndexOfItem(item, &itemIdx);
+ if (itemIdx >= 0)
+ aRows->ElementAt(rowIdx) = itemIdx;
+ }
+ }
+}
+
+void
+XULListboxAccessible::SelectRow(uint32_t aRowIdx)
+{
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> control =
+ do_QueryInterface(mContent);
+ NS_ASSERTION(control,
+ "Doesn't implement nsIDOMXULMultiSelectControlElement.");
+
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> item;
+ control->GetItemAtIndex(aRowIdx, getter_AddRefs(item));
+ control->SelectItem(item);
+}
+
+void
+XULListboxAccessible::UnselectRow(uint32_t aRowIdx)
+{
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> control =
+ do_QueryInterface(mContent);
+ NS_ASSERTION(control,
+ "Doesn't implement nsIDOMXULMultiSelectControlElement.");
+
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> item;
+ control->GetItemAtIndex(aRowIdx, getter_AddRefs(item));
+ control->RemoveItemFromSelection(item);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULListboxAccessible: Widgets
+
+bool
+XULListboxAccessible::IsWidget() const
+{
+ return true;
+}
+
+bool
+XULListboxAccessible::IsActiveWidget() const
+{
+ if (IsAutoCompletePopup()) {
+ nsCOMPtr<nsIAutoCompletePopup> autoCompletePopupElm =
+ do_QueryInterface(mContent->GetParent());
+
+ if (autoCompletePopupElm) {
+ bool isOpen = false;
+ autoCompletePopupElm->GetPopupOpen(&isOpen);
+ return isOpen;
+ }
+ }
+ return FocusMgr()->HasDOMFocus(mContent);
+}
+
+bool
+XULListboxAccessible::AreItemsOperable() const
+{
+ if (IsAutoCompletePopup()) {
+ nsCOMPtr<nsIAutoCompletePopup> autoCompletePopupElm =
+ do_QueryInterface(mContent->GetParent());
+
+ if (autoCompletePopupElm) {
+ bool isOpen = false;
+ autoCompletePopupElm->GetPopupOpen(&isOpen);
+ return isOpen;
+ }
+ }
+ return true;
+}
+
+Accessible*
+XULListboxAccessible::ContainerWidget() const
+{
+ if (IsAutoCompletePopup()) {
+ // This works for XUL autocompletes. It doesn't work for HTML forms
+ // autocomplete because of potential crossprocess calls (when autocomplete
+ // lives in content process while popup lives in chrome process). If that's
+ // a problem then rethink Widgets interface.
+ nsCOMPtr<nsIDOMXULMenuListElement> menuListElm =
+ do_QueryInterface(mContent->GetParent());
+ if (menuListElm) {
+ nsCOMPtr<nsIDOMNode> inputElm;
+ menuListElm->GetInputField(getter_AddRefs(inputElm));
+ if (inputElm) {
+ nsCOMPtr<nsINode> inputNode = do_QueryInterface(inputElm);
+ if (inputNode) {
+ Accessible* input =
+ mDoc->GetAccessible(inputNode);
+ return input ? input->ContainerWidget() : nullptr;
+ }
+ }
+ }
+ }
+ return nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULListitemAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULListitemAccessible::
+ XULListitemAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ XULMenuitemAccessible(aContent, aDoc)
+{
+ mIsCheckbox = mContent->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::type,
+ nsGkAtoms::checkbox,
+ eCaseMatters);
+ mType = eXULListItemType;
+
+ // Walk XBL anonymous children for list items. Overrides the flag value from
+ // base XULMenuitemAccessible class.
+ mStateFlags &= ~eNoXBLKids;
+}
+
+XULListitemAccessible::~XULListitemAccessible()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(XULListitemAccessible, Accessible)
+
+Accessible*
+XULListitemAccessible::GetListAccessible() const
+{
+ if (IsDefunct())
+ return nullptr;
+
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> listItem =
+ do_QueryInterface(mContent);
+ if (!listItem)
+ return nullptr;
+
+ nsCOMPtr<nsIDOMXULSelectControlElement> list;
+ listItem->GetControl(getter_AddRefs(list));
+
+ nsCOMPtr<nsIContent> listContent(do_QueryInterface(list));
+ if (!listContent)
+ return nullptr;
+
+ return mDoc->GetAccessible(listContent);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULListitemAccessible Accessible
+
+void
+XULListitemAccessible::Description(nsString& aDesc)
+{
+ AccessibleWrap::Description(aDesc);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULListitemAccessible: Accessible
+
+/**
+ * If there is a Listcell as a child ( not anonymous ) use it, otherwise
+ * default to getting the name from GetXULName
+ */
+ENameValueFlag
+XULListitemAccessible::NativeName(nsString& aName)
+{
+ nsIContent* childContent = mContent->GetFirstChild();
+ if (childContent) {
+ if (childContent->NodeInfo()->Equals(nsGkAtoms::listcell,
+ kNameSpaceID_XUL)) {
+ childContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aName);
+ return eNameOK;
+ }
+ }
+
+ return Accessible::NativeName(aName);
+}
+
+role
+XULListitemAccessible::NativeRole()
+{
+ Accessible* list = GetListAccessible();
+ if (!list) {
+ NS_ERROR("No list accessible for listitem accessible!");
+ return roles::NOTHING;
+ }
+
+ if (list->Role() == roles::TABLE)
+ return roles::ROW;
+
+ if (mIsCheckbox)
+ return roles::CHECK_RICH_OPTION;
+
+ if (mParent && mParent->Role() == roles::COMBOBOX_LIST)
+ return roles::COMBOBOX_OPTION;
+
+ return roles::RICH_OPTION;
+}
+
+uint64_t
+XULListitemAccessible::NativeState()
+{
+ if (mIsCheckbox)
+ return XULMenuitemAccessible::NativeState();
+
+ uint64_t states = NativeInteractiveState();
+
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> listItem =
+ do_QueryInterface(mContent);
+
+ if (listItem) {
+ bool isSelected;
+ listItem->GetSelected(&isSelected);
+ if (isSelected)
+ states |= states::SELECTED;
+
+ if (FocusMgr()->IsFocused(this))
+ states |= states::FOCUSED;
+ }
+
+ return states;
+}
+
+uint64_t
+XULListitemAccessible::NativeInteractiveState() const
+{
+ return NativelyUnavailable() || (mParent && mParent->NativelyUnavailable()) ?
+ states::UNAVAILABLE : states::FOCUSABLE | states::SELECTABLE;
+}
+
+void
+XULListitemAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ if (aIndex == eAction_Click && mIsCheckbox) {
+ uint64_t states = NativeState();
+ if (states & states::CHECKED)
+ aName.AssignLiteral("uncheck");
+ else
+ aName.AssignLiteral("check");
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULListitemAccessible: Widgets
+
+Accessible*
+XULListitemAccessible::ContainerWidget() const
+{
+ return Parent();
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// XULListCellAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULListCellAccessible::
+ XULListCellAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HyperTextAccessibleWrap(aContent, aDoc)
+{
+ mGenericTypes |= eTableCell;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED0(XULListCellAccessible,
+ HyperTextAccessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// XULListCellAccessible: TableCell
+
+TableAccessible*
+XULListCellAccessible::Table() const
+{
+ Accessible* thisRow = Parent();
+ if (!thisRow || thisRow->Role() != roles::ROW)
+ return nullptr;
+
+ Accessible* table = thisRow->Parent();
+ if (!table || table->Role() != roles::TABLE)
+ return nullptr;
+
+ return table->AsTable();
+}
+
+uint32_t
+XULListCellAccessible::ColIdx() const
+{
+ Accessible* row = Parent();
+ if (!row)
+ return 0;
+
+ int32_t indexInRow = IndexInParent();
+ uint32_t colIdx = 0;
+ for (int32_t idx = 0; idx < indexInRow; idx++) {
+ Accessible* cell = row->GetChildAt(idx);
+ roles::Role role = cell->Role();
+ if (role == roles::CELL || role == roles::GRID_CELL ||
+ role == roles::ROWHEADER || role == roles::COLUMNHEADER)
+ colIdx++;
+ }
+
+ return colIdx;
+}
+
+uint32_t
+XULListCellAccessible::RowIdx() const
+{
+ Accessible* row = Parent();
+ if (!row)
+ return 0;
+
+ Accessible* table = row->Parent();
+ if (!table)
+ return 0;
+
+ int32_t indexInTable = row->IndexInParent();
+ uint32_t rowIdx = 0;
+ for (int32_t idx = 0; idx < indexInTable; idx++) {
+ row = table->GetChildAt(idx);
+ if (row->Role() == roles::ROW)
+ rowIdx++;
+ }
+
+ return rowIdx;
+}
+
+void
+XULListCellAccessible::ColHeaderCells(nsTArray<Accessible*>* aCells)
+{
+ TableAccessible* table = Table();
+ NS_ASSERTION(table, "cell not in a table!");
+ if (!table)
+ return;
+
+ // Get column header cell from XUL listhead.
+ Accessible* list = nullptr;
+
+ Accessible* tableAcc = table->AsAccessible();
+ uint32_t tableChildCount = tableAcc->ChildCount();
+ for (uint32_t childIdx = 0; childIdx < tableChildCount; childIdx++) {
+ Accessible* child = tableAcc->GetChildAt(childIdx);
+ if (child->Role() == roles::LIST) {
+ list = child;
+ break;
+ }
+ }
+
+ if (list) {
+ Accessible* headerCell = list->GetChildAt(ColIdx());
+ if (headerCell) {
+ aCells->AppendElement(headerCell);
+ return;
+ }
+ }
+
+ // No column header cell from XUL markup, try to get it from ARIA markup.
+ TableCellAccessible::ColHeaderCells(aCells);
+}
+
+bool
+XULListCellAccessible::Selected()
+{
+ TableAccessible* table = Table();
+ NS_ENSURE_TRUE(table, false); // we expect to be in a listbox (table)
+
+ return table->IsRowSelected(RowIdx());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULListCellAccessible. Accessible implementation
+
+role
+XULListCellAccessible::NativeRole()
+{
+ return roles::CELL;
+}
+
+already_AddRefed<nsIPersistentProperties>
+XULListCellAccessible::NativeAttributes()
+{
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ HyperTextAccessibleWrap::NativeAttributes();
+
+ // "table-cell-index" attribute
+ TableAccessible* table = Table();
+ if (!table) // we expect to be in a listbox (table)
+ return attributes.forget();
+
+ nsAutoString stringIdx;
+ stringIdx.AppendInt(table->CellIndexAt(RowIdx(), ColIdx()));
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::tableCellIndex, stringIdx);
+
+ return attributes.forget();
+}
diff --git a/accessible/xul/XULListboxAccessible.h b/accessible/xul/XULListboxAccessible.h
new file mode 100644
index 000000000..057838035
--- /dev/null
+++ b/accessible/xul/XULListboxAccessible.h
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_XULListboxAccessible_h__
+#define mozilla_a11y_XULListboxAccessible_h__
+
+#include "BaseAccessibles.h"
+#include "TableAccessible.h"
+#include "TableCellAccessible.h"
+#include "xpcAccessibleTable.h"
+#include "xpcAccessibleTableCell.h"
+#include "XULMenuAccessible.h"
+#include "XULSelectControlAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * XULColumAccessible are accessible for list and tree columns elements
+ * (xul:treecols and xul:listcols).
+ */
+class XULColumAccessible : public AccessibleWrap
+{
+public:
+ XULColumAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+};
+
+/**
+ * XULColumnItemAccessible are accessibles for list and tree column elements
+ * (xul:listcol and xul:treecol).
+ */
+class XULColumnItemAccessible : public LeafAccessible
+{
+public:
+ XULColumnItemAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+
+ enum { eAction_Click = 0 };
+};
+
+/*
+ * A class the represents the XUL Listbox widget.
+ */
+class XULListboxAccessible : public XULSelectControlAccessible,
+ public TableAccessible
+{
+public:
+ XULListboxAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // TableAccessible
+ virtual uint32_t ColCount() override;
+ virtual uint32_t RowCount() override;
+ virtual Accessible* CellAt(uint32_t aRowIndex, uint32_t aColumnIndex) override;
+ virtual bool IsColSelected(uint32_t aColIdx) override;
+ virtual bool IsRowSelected(uint32_t aRowIdx) override;
+ virtual bool IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) override;
+ virtual uint32_t SelectedCellCount() override;
+ virtual uint32_t SelectedColCount() override;
+ virtual uint32_t SelectedRowCount() override;
+ virtual void SelectedCells(nsTArray<Accessible*>* aCells) override;
+ virtual void SelectedCellIndices(nsTArray<uint32_t>* aCells) override;
+ virtual void SelectedColIndices(nsTArray<uint32_t>* aCols) override;
+ virtual void SelectedRowIndices(nsTArray<uint32_t>* aRows) override;
+ virtual void SelectRow(uint32_t aRowIdx) override;
+ virtual void UnselectRow(uint32_t aRowIdx) override;
+ virtual Accessible* AsAccessible() override { return this; }
+
+ // Accessible
+ virtual void Value(nsString& aValue) override;
+ virtual TableAccessible* AsTable() override { return this; }
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+
+ // Widgets
+ virtual bool IsWidget() const override;
+ virtual bool IsActiveWidget() const override;
+ virtual bool AreItemsOperable() const override;
+
+ virtual Accessible* ContainerWidget() const override;
+
+protected:
+ virtual ~XULListboxAccessible() {}
+
+ bool IsMulticolumn() { return ColCount() > 1; }
+};
+
+/**
+ * Listitems -- used in listboxes
+ */
+class XULListitemAccessible : public XULMenuitemAccessible
+{
+public:
+ enum { eAction_Click = 0 };
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ XULListitemAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual void Description(nsString& aDesc) override;
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+ virtual uint64_t NativeInteractiveState() const override;
+
+ // Actions
+ virtual void ActionNameAt(uint8_t index, nsAString& aName) override;
+
+ // Widgets
+ virtual Accessible* ContainerWidget() const override;
+
+protected:
+ virtual ~XULListitemAccessible();
+
+ // Accessible
+ virtual ENameValueFlag NativeName(nsString& aName) override;
+
+ // XULListitemAccessible
+
+ /**
+ * Return listbox accessible for the listitem.
+ */
+ Accessible* GetListAccessible() const;
+
+private:
+ bool mIsCheckbox;
+};
+
+/**
+ * Class represents xul:listcell.
+ */
+class XULListCellAccessible : public HyperTextAccessibleWrap,
+ public TableCellAccessible
+{
+public:
+ XULListCellAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // Accessible
+ virtual TableCellAccessible* AsTableCell() override { return this; }
+ virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override;
+ virtual a11y::role NativeRole() override;
+
+ // TableCellAccessible
+ virtual TableAccessible* Table() const override;
+ virtual uint32_t ColIdx() const override;
+ virtual uint32_t RowIdx() const override;
+ virtual void ColHeaderCells(nsTArray<Accessible*>* aHeaderCells) override;
+ virtual bool Selected() override;
+
+protected:
+ virtual ~XULListCellAccessible() {}
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/xul/XULMenuAccessible.cpp b/accessible/xul/XULMenuAccessible.cpp
new file mode 100644
index 000000000..f93e9ad99
--- /dev/null
+++ b/accessible/xul/XULMenuAccessible.cpp
@@ -0,0 +1,591 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "XULMenuAccessible.h"
+
+#include "Accessible-inl.h"
+#include "nsAccessibilityService.h"
+#include "nsAccUtils.h"
+#include "DocAccessible.h"
+#include "Role.h"
+#include "States.h"
+#include "XULFormControlAccessible.h"
+
+#include "nsIDOMElement.h"
+#include "nsIDOMXULElement.h"
+#include "nsIMutableArray.h"
+#include "nsIDOMXULContainerElement.h"
+#include "nsIDOMXULSelectCntrlItemEl.h"
+#include "nsIDOMXULMultSelectCntrlEl.h"
+#include "nsIDOMKeyEvent.h"
+#include "nsIServiceManager.h"
+#include "nsIPresShell.h"
+#include "nsIContent.h"
+#include "nsMenuBarFrame.h"
+#include "nsMenuPopupFrame.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// XULMenuitemAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULMenuitemAccessible::
+ XULMenuitemAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+ mStateFlags |= eNoXBLKids;
+}
+
+uint64_t
+XULMenuitemAccessible::NativeState()
+{
+ uint64_t state = Accessible::NativeState();
+
+ // Has Popup?
+ if (mContent->NodeInfo()->Equals(nsGkAtoms::menu, kNameSpaceID_XUL)) {
+ state |= states::HASPOPUP;
+ if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::open))
+ state |= states::EXPANDED;
+ else
+ state |= states::COLLAPSED;
+ }
+
+ // Checkable/checked?
+ static nsIContent::AttrValuesArray strings[] =
+ { &nsGkAtoms::radio, &nsGkAtoms::checkbox, nullptr };
+
+ if (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type, strings,
+ eCaseMatters) >= 0) {
+
+ // Checkable?
+ state |= states::CHECKABLE;
+
+ // Checked?
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
+ nsGkAtoms::_true, eCaseMatters))
+ state |= states::CHECKED;
+ }
+
+ // Combo box listitem
+ bool isComboboxOption = (Role() == roles::COMBOBOX_OPTION);
+ if (isComboboxOption) {
+ // Is selected?
+ bool isSelected = false;
+ nsCOMPtr<nsIDOMXULSelectControlItemElement>
+ item(do_QueryInterface(mContent));
+ NS_ENSURE_TRUE(item, state);
+ item->GetSelected(&isSelected);
+
+ // Is collapsed?
+ bool isCollapsed = false;
+ Accessible* parent = Parent();
+ if (parent && parent->State() & states::INVISIBLE)
+ isCollapsed = true;
+
+ if (isSelected) {
+ state |= states::SELECTED;
+
+ // Selected and collapsed?
+ if (isCollapsed) {
+ // Set selected option offscreen/invisible according to combobox state
+ Accessible* grandParent = parent->Parent();
+ if (!grandParent)
+ return state;
+ NS_ASSERTION(grandParent->Role() == roles::COMBOBOX,
+ "grandparent of combobox listitem is not combobox");
+ uint64_t grandParentState = grandParent->State();
+ state &= ~(states::OFFSCREEN | states::INVISIBLE);
+ state |= (grandParentState & states::OFFSCREEN) |
+ (grandParentState & states::INVISIBLE) |
+ (grandParentState & states::OPAQUE1);
+ } // isCollapsed
+ } // isSelected
+ } // ROLE_COMBOBOX_OPTION
+
+ return state;
+}
+
+uint64_t
+XULMenuitemAccessible::NativeInteractiveState() const
+{
+ if (NativelyUnavailable()) {
+ // Note: keep in sinc with nsXULPopupManager::IsValidMenuItem() logic.
+ bool skipNavigatingDisabledMenuItem = true;
+ nsMenuFrame* menuFrame = do_QueryFrame(GetFrame());
+ if (!menuFrame || !menuFrame->IsOnMenuBar()) {
+ skipNavigatingDisabledMenuItem = LookAndFeel::
+ GetInt(LookAndFeel::eIntID_SkipNavigatingDisabledMenuItem, 0) != 0;
+ }
+
+ if (skipNavigatingDisabledMenuItem)
+ return states::UNAVAILABLE;
+
+ return states::UNAVAILABLE | states::FOCUSABLE | states::SELECTABLE;
+ }
+
+ return states::FOCUSABLE | states::SELECTABLE;
+}
+
+ENameValueFlag
+XULMenuitemAccessible::NativeName(nsString& aName)
+{
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aName);
+ return eNameOK;
+}
+
+void
+XULMenuitemAccessible::Description(nsString& aDescription)
+{
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::description,
+ aDescription);
+}
+
+KeyBinding
+XULMenuitemAccessible::AccessKey() const
+{
+ // Return menu accesskey: N or Alt+F.
+ static int32_t gMenuAccesskeyModifier = -1; // magic value of -1 indicates unitialized state
+
+ // We do not use nsCoreUtils::GetAccesskeyFor() because accesskeys for
+ // menu are't registered by EventStateManager.
+ nsAutoString accesskey;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey,
+ accesskey);
+ if (accesskey.IsEmpty())
+ return KeyBinding();
+
+ uint32_t modifierKey = 0;
+
+ Accessible* parentAcc = Parent();
+ if (parentAcc) {
+ if (parentAcc->NativeRole() == roles::MENUBAR) {
+ // If top level menu item, add Alt+ or whatever modifier text to string
+ // No need to cache pref service, this happens rarely
+ if (gMenuAccesskeyModifier == -1) {
+ // Need to initialize cached global accesskey pref
+ gMenuAccesskeyModifier = Preferences::GetInt("ui.key.menuAccessKey", 0);
+ }
+
+ switch (gMenuAccesskeyModifier) {
+ case nsIDOMKeyEvent::DOM_VK_CONTROL:
+ modifierKey = KeyBinding::kControl;
+ break;
+ case nsIDOMKeyEvent::DOM_VK_ALT:
+ modifierKey = KeyBinding::kAlt;
+ break;
+ case nsIDOMKeyEvent::DOM_VK_META:
+ modifierKey = KeyBinding::kMeta;
+ break;
+ case nsIDOMKeyEvent::DOM_VK_WIN:
+ modifierKey = KeyBinding::kOS;
+ break;
+ }
+ }
+ }
+
+ return KeyBinding(accesskey[0], modifierKey);
+}
+
+KeyBinding
+XULMenuitemAccessible::KeyboardShortcut() const
+{
+ nsAutoString keyElmId;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyElmId);
+ if (keyElmId.IsEmpty())
+ return KeyBinding();
+
+ nsIContent* keyElm = mContent->OwnerDoc()->GetElementById(keyElmId);
+ if (!keyElm)
+ return KeyBinding();
+
+ uint32_t key = 0;
+
+ nsAutoString keyStr;
+ keyElm->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyStr);
+ if (keyStr.IsEmpty()) {
+ nsAutoString keyCodeStr;
+ keyElm->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, keyCodeStr);
+ nsresult errorCode;
+ key = keyStr.ToInteger(&errorCode, kAutoDetect);
+ } else {
+ key = keyStr[0];
+ }
+
+ nsAutoString modifiersStr;
+ keyElm->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiersStr);
+
+ uint32_t modifierMask = 0;
+ if (modifiersStr.Find("shift") != -1)
+ modifierMask |= KeyBinding::kShift;
+ if (modifiersStr.Find("alt") != -1)
+ modifierMask |= KeyBinding::kAlt;
+ if (modifiersStr.Find("meta") != -1)
+ modifierMask |= KeyBinding::kMeta;
+ if (modifiersStr.Find("os") != -1)
+ modifierMask |= KeyBinding::kOS;
+ if (modifiersStr.Find("control") != -1)
+ modifierMask |= KeyBinding::kControl;
+ if (modifiersStr.Find("accel") != -1) {
+ modifierMask |= KeyBinding::AccelModifier();
+ }
+
+ return KeyBinding(key, modifierMask);
+}
+
+role
+XULMenuitemAccessible::NativeRole()
+{
+ nsCOMPtr<nsIDOMXULContainerElement> xulContainer(do_QueryInterface(mContent));
+ if (xulContainer)
+ return roles::PARENT_MENUITEM;
+
+ if (mParent && mParent->Role() == roles::COMBOBOX_LIST)
+ return roles::COMBOBOX_OPTION;
+
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::radio, eCaseMatters))
+ return roles::RADIO_MENU_ITEM;
+
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::checkbox,
+ eCaseMatters))
+ return roles::CHECK_MENU_ITEM;
+
+ return roles::MENUITEM;
+}
+
+int32_t
+XULMenuitemAccessible::GetLevelInternal()
+{
+ return nsAccUtils::GetLevelForXULContainerItem(mContent);
+}
+
+bool
+XULMenuitemAccessible::DoAction(uint8_t index)
+{
+ if (index == eAction_Click) { // default action
+ DoCommand();
+ return true;
+ }
+
+ return false;
+}
+
+void
+XULMenuitemAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ if (aIndex == eAction_Click)
+ aName.AssignLiteral("click");
+}
+
+uint8_t
+XULMenuitemAccessible::ActionCount()
+{
+ return 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULMenuitemAccessible: Widgets
+
+bool
+XULMenuitemAccessible::IsActiveWidget() const
+{
+ // Parent menu item is a widget, it's active when its popup is open.
+ nsIContent* menuPopupContent = mContent->GetFirstChild();
+ if (menuPopupContent) {
+ nsMenuPopupFrame* menuPopupFrame =
+ do_QueryFrame(menuPopupContent->GetPrimaryFrame());
+ return menuPopupFrame && menuPopupFrame->IsOpen();
+ }
+ return false;
+}
+
+bool
+XULMenuitemAccessible::AreItemsOperable() const
+{
+ // Parent menu item is a widget, its items are operable when its popup is open.
+ nsIContent* menuPopupContent = mContent->GetFirstChild();
+ if (menuPopupContent) {
+ nsMenuPopupFrame* menuPopupFrame =
+ do_QueryFrame(menuPopupContent->GetPrimaryFrame());
+ return menuPopupFrame && menuPopupFrame->IsOpen();
+ }
+ return false;
+}
+
+Accessible*
+XULMenuitemAccessible::ContainerWidget() const
+{
+ nsMenuFrame* menuFrame = do_QueryFrame(GetFrame());
+ if (menuFrame) {
+ nsMenuParent* menuParent = menuFrame->GetMenuParent();
+ if (menuParent) {
+ if (menuParent->IsMenuBar()) // menubar menu
+ return mParent;
+
+ // a menupoup or parent menu item
+ if (menuParent->IsMenu())
+ return mParent;
+
+ // otherwise it's different kind of popups (like panel or tooltip), it
+ // shouldn't be a real case.
+ }
+ }
+ return nullptr;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// XULMenuSeparatorAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULMenuSeparatorAccessible::
+ XULMenuSeparatorAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ XULMenuitemAccessible(aContent, aDoc)
+{
+}
+
+uint64_t
+XULMenuSeparatorAccessible::NativeState()
+{
+ // Isn't focusable, but can be offscreen/invisible -- only copy those states
+ return XULMenuitemAccessible::NativeState() &
+ (states::OFFSCREEN | states::INVISIBLE);
+}
+
+ENameValueFlag
+XULMenuSeparatorAccessible::NativeName(nsString& aName)
+{
+ return eNameOK;
+}
+
+role
+XULMenuSeparatorAccessible::NativeRole()
+{
+ return roles::SEPARATOR;
+}
+
+bool
+XULMenuSeparatorAccessible::DoAction(uint8_t index)
+{
+ return false;
+}
+
+void
+XULMenuSeparatorAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ aName.Truncate();
+}
+
+uint8_t
+XULMenuSeparatorAccessible::ActionCount()
+{
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULMenupopupAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULMenupopupAccessible::
+ XULMenupopupAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ XULSelectControlAccessible(aContent, aDoc)
+{
+ nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame());
+ if (menuPopupFrame && menuPopupFrame->IsMenu())
+ mType = eMenuPopupType;
+
+ // May be the anonymous <menupopup> inside <menulist> (a combobox)
+ mSelectControl = do_QueryInterface(mContent->GetFlattenedTreeParent());
+ if (!mSelectControl)
+ mGenericTypes &= ~eSelect;
+
+ mStateFlags |= eNoXBLKids;
+}
+
+uint64_t
+XULMenupopupAccessible::NativeState()
+{
+ uint64_t state = Accessible::NativeState();
+
+#ifdef DEBUG
+ // We are onscreen if our parent is active
+ bool isActive = mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::menuactive);
+ if (!isActive) {
+ Accessible* parent = Parent();
+ if (parent) {
+ nsIContent* parentContent = parent->GetContent();
+ if (parentContent)
+ isActive = parentContent->HasAttr(kNameSpaceID_None, nsGkAtoms::open);
+ }
+ }
+
+ NS_ASSERTION(isActive || (state & states::INVISIBLE),
+ "XULMenupopup doesn't have INVISIBLE when it's inactive");
+#endif
+
+ if (state & states::INVISIBLE)
+ state |= states::OFFSCREEN | states::COLLAPSED;
+
+ return state;
+}
+
+ENameValueFlag
+XULMenupopupAccessible::NativeName(nsString& aName)
+{
+ nsIContent* content = mContent;
+ while (content && aName.IsEmpty()) {
+ content->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aName);
+ content = content->GetFlattenedTreeParent();
+ }
+
+ return eNameOK;
+}
+
+role
+XULMenupopupAccessible::NativeRole()
+{
+ // If accessible is not bound to the tree (this happens while children are
+ // cached) return general role.
+ if (mParent) {
+ roles::Role role = mParent->Role();
+ if (role == roles::COMBOBOX || role == roles::AUTOCOMPLETE)
+ return roles::COMBOBOX_LIST;
+
+ if (role == roles::PUSHBUTTON) {
+ // Some widgets like the search bar have several popups, owned by buttons.
+ Accessible* grandParent = mParent->Parent();
+ if (grandParent && grandParent->Role() == roles::AUTOCOMPLETE)
+ return roles::COMBOBOX_LIST;
+ }
+ }
+
+ return roles::MENUPOPUP;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULMenupopupAccessible: Widgets
+
+bool
+XULMenupopupAccessible::IsWidget() const
+{
+ return true;
+}
+
+bool
+XULMenupopupAccessible::IsActiveWidget() const
+{
+ // If menupopup is a widget (the case of context menus) then active when open.
+ nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame());
+ return menuPopupFrame && menuPopupFrame->IsOpen();
+}
+
+bool
+XULMenupopupAccessible::AreItemsOperable() const
+{
+ nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame());
+ return menuPopupFrame && menuPopupFrame->IsOpen();
+}
+
+Accessible*
+XULMenupopupAccessible::ContainerWidget() const
+{
+ DocAccessible* document = Document();
+
+ nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame());
+ while (menuPopupFrame) {
+ Accessible* menuPopup =
+ document->GetAccessible(menuPopupFrame->GetContent());
+ if (!menuPopup) // shouldn't be a real case
+ return nullptr;
+
+ nsMenuFrame* menuFrame = do_QueryFrame(menuPopupFrame->GetParent());
+ if (!menuFrame) // context menu or popups
+ return nullptr;
+
+ nsMenuParent* menuParent = menuFrame->GetMenuParent();
+ if (!menuParent) // menulist or menubutton
+ return menuPopup->Parent();
+
+ if (menuParent->IsMenuBar()) { // menubar menu
+ nsMenuBarFrame* menuBarFrame = static_cast<nsMenuBarFrame*>(menuParent);
+ return document->GetAccessible(menuBarFrame->GetContent());
+ }
+
+ // different kind of popups like panel or tooltip
+ if (!menuParent->IsMenu())
+ return nullptr;
+
+ menuPopupFrame = static_cast<nsMenuPopupFrame*>(menuParent);
+ }
+
+ NS_NOTREACHED("Shouldn't be a real case.");
+ return nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULMenubarAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULMenubarAccessible::
+ XULMenubarAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+}
+
+ENameValueFlag
+XULMenubarAccessible::NativeName(nsString& aName)
+{
+ aName.AssignLiteral("Application");
+ return eNameOK;
+}
+
+role
+XULMenubarAccessible::NativeRole()
+{
+ return roles::MENUBAR;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULMenubarAccessible: Widgets
+
+bool
+XULMenubarAccessible::IsActiveWidget() const
+{
+ nsMenuBarFrame* menuBarFrame = do_QueryFrame(GetFrame());
+ return menuBarFrame && menuBarFrame->IsActive();
+}
+
+bool
+XULMenubarAccessible::AreItemsOperable() const
+{
+ return true;
+}
+
+Accessible*
+XULMenubarAccessible::CurrentItem()
+{
+ nsMenuBarFrame* menuBarFrame = do_QueryFrame(GetFrame());
+ if (menuBarFrame) {
+ nsMenuFrame* menuFrame = menuBarFrame->GetCurrentMenuItem();
+ if (menuFrame) {
+ nsIContent* menuItemNode = menuFrame->GetContent();
+ return mDoc->GetAccessible(menuItemNode);
+ }
+ }
+ return nullptr;
+}
+
+void
+XULMenubarAccessible::SetCurrentItem(Accessible* aItem)
+{
+ NS_ERROR("XULMenubarAccessible::SetCurrentItem not implemented");
+}
diff --git a/accessible/xul/XULMenuAccessible.h b/accessible/xul/XULMenuAccessible.h
new file mode 100644
index 000000000..6280db17d
--- /dev/null
+++ b/accessible/xul/XULMenuAccessible.h
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_XULMenuAccessible_h__
+#define mozilla_a11y_XULMenuAccessible_h__
+
+#include "AccessibleWrap.h"
+#include "nsIDOMXULSelectCntrlEl.h"
+#include "XULSelectControlAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Used for XUL menu, menuitem elements.
+ */
+class XULMenuitemAccessible : public AccessibleWrap
+{
+public:
+ enum { eAction_Click = 0 };
+
+ XULMenuitemAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual void Description(nsString& aDescription) override;
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+ virtual uint64_t NativeInteractiveState() const override;
+ virtual int32_t GetLevelInternal() override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+ virtual KeyBinding AccessKey() const override;
+ virtual KeyBinding KeyboardShortcut() const override;
+
+ // Widgets
+ virtual bool IsActiveWidget() const override;
+ virtual bool AreItemsOperable() const override;
+ virtual Accessible* ContainerWidget() const override;
+
+protected:
+ // Accessible
+ virtual ENameValueFlag NativeName(nsString& aName) override;
+};
+
+/**
+ * Used for XUL menuseparator element.
+ */
+class XULMenuSeparatorAccessible : public XULMenuitemAccessible
+{
+public:
+ XULMenuSeparatorAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+
+protected:
+ // Accessible
+ virtual ENameValueFlag NativeName(nsString& aName) override;
+};
+
+
+/**
+ * Used for XUL menupopup and panel.
+ */
+class XULMenupopupAccessible : public XULSelectControlAccessible
+{
+public:
+ XULMenupopupAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+
+ // Widgets
+ virtual bool IsWidget() const override;
+ virtual bool IsActiveWidget() const override;
+ virtual bool AreItemsOperable() const override;
+
+ virtual Accessible* ContainerWidget() const override;
+
+protected:
+ // Accessible
+ virtual ENameValueFlag NativeName(nsString& aName) override;
+};
+
+/**
+ * Used for XUL menubar element.
+ */
+class XULMenubarAccessible : public AccessibleWrap
+{
+public:
+ XULMenubarAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+
+ // Widget
+ virtual bool IsActiveWidget() const override;
+ virtual bool AreItemsOperable() const override;
+ virtual Accessible* CurrentItem() override;
+ virtual void SetCurrentItem(Accessible* aItem) override;
+
+protected:
+ // Accessible
+ virtual ENameValueFlag NativeName(nsString& aName) override;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/xul/XULSelectControlAccessible.cpp b/accessible/xul/XULSelectControlAccessible.cpp
new file mode 100644
index 000000000..a1cc65d78
--- /dev/null
+++ b/accessible/xul/XULSelectControlAccessible.cpp
@@ -0,0 +1,256 @@
+/* -*- 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 "XULSelectControlAccessible.h"
+
+#include "nsAccessibilityService.h"
+#include "DocAccessible.h"
+
+#include "nsIDOMXULContainerElement.h"
+#include "nsIDOMXULSelectCntrlItemEl.h"
+#include "nsIDOMXULMultSelectCntrlEl.h"
+#include "nsIDOMKeyEvent.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMXULElement.h"
+#include "nsIMutableArray.h"
+#include "nsIServiceManager.h"
+
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// XULSelectControlAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULSelectControlAccessible::
+ XULSelectControlAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+ mGenericTypes |= eSelect;
+ mSelectControl = do_QueryInterface(aContent);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULSelectControlAccessible: Accessible
+
+void
+XULSelectControlAccessible::Shutdown()
+{
+ mSelectControl = nullptr;
+ AccessibleWrap::Shutdown();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULSelectControlAccessible: SelectAccessible
+
+void
+XULSelectControlAccessible::SelectedItems(nsTArray<Accessible*>* aItems)
+{
+ // For XUL multi-select control
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> xulMultiSelect =
+ do_QueryInterface(mSelectControl);
+ if (xulMultiSelect) {
+ int32_t length = 0;
+ xulMultiSelect->GetSelectedCount(&length);
+ for (int32_t index = 0; index < length; index++) {
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> itemElm;
+ xulMultiSelect->MultiGetSelectedItem(index, getter_AddRefs(itemElm));
+ nsCOMPtr<nsINode> itemNode(do_QueryInterface(itemElm));
+ Accessible* item = mDoc->GetAccessible(itemNode);
+ if (item)
+ aItems->AppendElement(item);
+ }
+ } else { // Single select?
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> itemElm;
+ mSelectControl->GetSelectedItem(getter_AddRefs(itemElm));
+ nsCOMPtr<nsINode> itemNode(do_QueryInterface(itemElm));
+ if (itemNode) {
+ Accessible* item = mDoc->GetAccessible(itemNode);
+ if (item)
+ aItems->AppendElement(item);
+ }
+ }
+}
+
+Accessible*
+XULSelectControlAccessible::GetSelectedItem(uint32_t aIndex)
+{
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelectControl =
+ do_QueryInterface(mSelectControl);
+
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> itemElm;
+ if (multiSelectControl)
+ multiSelectControl->MultiGetSelectedItem(aIndex, getter_AddRefs(itemElm));
+ else if (aIndex == 0)
+ mSelectControl->GetSelectedItem(getter_AddRefs(itemElm));
+
+ nsCOMPtr<nsINode> itemNode(do_QueryInterface(itemElm));
+ return itemNode && mDoc ? mDoc->GetAccessible(itemNode) : nullptr;
+}
+
+uint32_t
+XULSelectControlAccessible::SelectedItemCount()
+{
+ // For XUL multi-select control
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelectControl =
+ do_QueryInterface(mSelectControl);
+ if (multiSelectControl) {
+ int32_t count = 0;
+ multiSelectControl->GetSelectedCount(&count);
+ return count;
+ }
+
+ // For XUL single-select control/menulist
+ int32_t index;
+ mSelectControl->GetSelectedIndex(&index);
+ return (index >= 0) ? 1 : 0;
+}
+
+bool
+XULSelectControlAccessible::AddItemToSelection(uint32_t aIndex)
+{
+ Accessible* item = GetChildAt(aIndex);
+ if (!item)
+ return false;
+
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> itemElm =
+ do_QueryInterface(item->GetContent());
+ if (!itemElm)
+ return false;
+
+ bool isItemSelected = false;
+ itemElm->GetSelected(&isItemSelected);
+ if (isItemSelected)
+ return true;
+
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelectControl =
+ do_QueryInterface(mSelectControl);
+
+ if (multiSelectControl)
+ multiSelectControl->AddItemToSelection(itemElm);
+ else
+ mSelectControl->SetSelectedItem(itemElm);
+
+ return true;
+}
+
+bool
+XULSelectControlAccessible::RemoveItemFromSelection(uint32_t aIndex)
+{
+ Accessible* item = GetChildAt(aIndex);
+ if (!item)
+ return false;
+
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> itemElm =
+ do_QueryInterface(item->GetContent());
+ if (!itemElm)
+ return false;
+
+ bool isItemSelected = false;
+ itemElm->GetSelected(&isItemSelected);
+ if (!isItemSelected)
+ return true;
+
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelectControl =
+ do_QueryInterface(mSelectControl);
+
+ if (multiSelectControl)
+ multiSelectControl->RemoveItemFromSelection(itemElm);
+ else
+ mSelectControl->SetSelectedItem(nullptr);
+
+ return true;
+}
+
+bool
+XULSelectControlAccessible::IsItemSelected(uint32_t aIndex)
+{
+ Accessible* item = GetChildAt(aIndex);
+ if (!item)
+ return false;
+
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> itemElm =
+ do_QueryInterface(item->GetContent());
+ if (!itemElm)
+ return false;
+
+ bool isItemSelected = false;
+ itemElm->GetSelected(&isItemSelected);
+ return isItemSelected;
+}
+
+bool
+XULSelectControlAccessible::UnselectAll()
+{
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelectControl =
+ do_QueryInterface(mSelectControl);
+ multiSelectControl ?
+ multiSelectControl->ClearSelection() : mSelectControl->SetSelectedIndex(-1);
+
+ return true;
+}
+
+bool
+XULSelectControlAccessible::SelectAll()
+{
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelectControl =
+ do_QueryInterface(mSelectControl);
+ if (multiSelectControl) {
+ multiSelectControl->SelectAll();
+ return true;
+ }
+
+ // otherwise, don't support this method
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULSelectControlAccessible: Widgets
+
+Accessible*
+XULSelectControlAccessible::CurrentItem()
+{
+ if (!mSelectControl)
+ return nullptr;
+
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> currentItemElm;
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelectControl =
+ do_QueryInterface(mSelectControl);
+ if (multiSelectControl)
+ multiSelectControl->GetCurrentItem(getter_AddRefs(currentItemElm));
+ else
+ mSelectControl->GetSelectedItem(getter_AddRefs(currentItemElm));
+
+ nsCOMPtr<nsINode> DOMNode;
+ if (currentItemElm)
+ DOMNode = do_QueryInterface(currentItemElm);
+
+ if (DOMNode) {
+ DocAccessible* document = Document();
+ if (document)
+ return document->GetAccessible(DOMNode);
+ }
+
+ return nullptr;
+}
+
+void
+XULSelectControlAccessible::SetCurrentItem(Accessible* aItem)
+{
+ if (!mSelectControl)
+ return;
+
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> itemElm =
+ do_QueryInterface(aItem->GetContent());
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelectControl =
+ do_QueryInterface(mSelectControl);
+ if (multiSelectControl)
+ multiSelectControl->SetCurrentItem(itemElm);
+ else
+ mSelectControl->SetSelectedItem(itemElm);
+}
diff --git a/accessible/xul/XULSelectControlAccessible.h b/accessible/xul/XULSelectControlAccessible.h
new file mode 100644
index 000000000..c4886e26e
--- /dev/null
+++ b/accessible/xul/XULSelectControlAccessible.h
@@ -0,0 +1,52 @@
+/* -*- 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_XULSelectControlAccessible_h__
+#define mozilla_a11y_XULSelectControlAccessible_h__
+
+#include "AccessibleWrap.h"
+#include "nsIDOMXULSelectCntrlEl.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * The basic implementation of accessible selection for XUL select controls.
+ */
+class XULSelectControlAccessible : public AccessibleWrap
+{
+public:
+ XULSelectControlAccessible(nsIContent* aContent, DocAccessible* aDoc);
+ virtual ~XULSelectControlAccessible() {}
+
+ // Accessible
+ virtual void Shutdown() override;
+
+ // SelectAccessible
+ virtual void SelectedItems(nsTArray<Accessible*>* aItems) override;
+ virtual uint32_t SelectedItemCount() override;
+ virtual Accessible* GetSelectedItem(uint32_t aIndex) override;
+ virtual bool IsItemSelected(uint32_t aIndex) override;
+ virtual bool AddItemToSelection(uint32_t aIndex) override;
+ virtual bool RemoveItemFromSelection(uint32_t aIndex) override;
+ virtual bool SelectAll() override;
+ virtual bool UnselectAll() override;
+
+ // Widgets
+ virtual Accessible* CurrentItem() override;
+ virtual void SetCurrentItem(Accessible* aItem) override;
+
+protected:
+ // nsIDOMXULMultiSelectControlElement inherits from this, so we'll always have
+ // one of these if the widget is valid and not defunct
+ nsCOMPtr<nsIDOMXULSelectControlElement> mSelectControl;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/xul/XULSliderAccessible.cpp b/accessible/xul/XULSliderAccessible.cpp
new file mode 100644
index 000000000..476cb17eb
--- /dev/null
+++ b/accessible/xul/XULSliderAccessible.cpp
@@ -0,0 +1,214 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "XULSliderAccessible.h"
+
+#include "nsAccessibilityService.h"
+#include "Role.h"
+#include "States.h"
+
+#include "nsIFrame.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/FloatingPoint.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// XULSliderAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULSliderAccessible::
+ XULSliderAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+ mStateFlags |= eHasNumericValue | eNoXBLKids;
+}
+
+// Accessible
+
+role
+XULSliderAccessible::NativeRole()
+{
+ return roles::SLIDER;
+}
+
+uint64_t
+XULSliderAccessible::NativeInteractiveState() const
+ {
+ if (NativelyUnavailable())
+ return states::UNAVAILABLE;
+
+ nsIContent* sliderElm = GetSliderElement();
+ if (sliderElm) {
+ nsIFrame* frame = sliderElm->GetPrimaryFrame();
+ if (frame && frame->IsFocusable())
+ return states::FOCUSABLE;
+ }
+
+ return 0;
+}
+
+bool
+XULSliderAccessible::NativelyUnavailable() const
+{
+ return mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters);
+}
+
+void
+XULSliderAccessible::Value(nsString& aValue)
+{
+ GetSliderAttr(nsGkAtoms::curpos, aValue);
+}
+
+uint8_t
+XULSliderAccessible::ActionCount()
+{
+ return 1;
+}
+
+void
+XULSliderAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ aName.Truncate();
+ if (aIndex == 0)
+ aName.AssignLiteral("activate");
+}
+
+bool
+XULSliderAccessible::DoAction(uint8_t aIndex)
+{
+ if (aIndex != 0)
+ return false;
+
+ nsIContent* sliderElm = GetSliderElement();
+ if (sliderElm)
+ DoCommand(sliderElm);
+
+ return true;
+}
+
+double
+XULSliderAccessible::MaxValue() const
+{
+ double value = AccessibleWrap::MaxValue();
+ return IsNaN(value) ? GetSliderAttr(nsGkAtoms::maxpos) : value;
+}
+
+double
+XULSliderAccessible::MinValue() const
+{
+ double value = AccessibleWrap::MinValue();
+ return IsNaN(value) ? GetSliderAttr(nsGkAtoms::minpos) : value;
+}
+
+double
+XULSliderAccessible::Step() const
+{
+ double value = AccessibleWrap::Step();
+ return IsNaN(value) ? GetSliderAttr(nsGkAtoms::increment) : value;
+}
+
+double
+XULSliderAccessible::CurValue() const
+{
+ double value = AccessibleWrap::CurValue();
+ return IsNaN(value) ? GetSliderAttr(nsGkAtoms::curpos) : value;
+}
+
+bool
+XULSliderAccessible::SetCurValue(double aValue)
+{
+ if (AccessibleWrap::SetCurValue(aValue))
+ return true;
+
+ return SetSliderAttr(nsGkAtoms::curpos, aValue);
+}
+
+// Utils
+
+nsIContent*
+XULSliderAccessible::GetSliderElement() const
+{
+ if (!mSliderNode) {
+ // XXX: we depend on anonymous content.
+ mSliderNode = mContent->OwnerDoc()->
+ GetAnonymousElementByAttribute(mContent, nsGkAtoms::anonid,
+ NS_LITERAL_STRING("slider"));
+ }
+
+ return mSliderNode;
+}
+
+nsresult
+XULSliderAccessible::GetSliderAttr(nsIAtom* aName, nsAString& aValue) const
+{
+ aValue.Truncate();
+
+ if (IsDefunct())
+ return NS_ERROR_FAILURE;
+
+ nsIContent* sliderElm = GetSliderElement();
+ if (sliderElm)
+ sliderElm->GetAttr(kNameSpaceID_None, aName, aValue);
+
+ return NS_OK;
+}
+
+nsresult
+XULSliderAccessible::SetSliderAttr(nsIAtom* aName, const nsAString& aValue)
+{
+ if (IsDefunct())
+ return NS_ERROR_FAILURE;
+
+ nsIContent* sliderElm = GetSliderElement();
+ if (sliderElm)
+ sliderElm->SetAttr(kNameSpaceID_None, aName, aValue, true);
+
+ return NS_OK;
+}
+
+double
+XULSliderAccessible::GetSliderAttr(nsIAtom* aName) const
+{
+ nsAutoString attrValue;
+ nsresult rv = GetSliderAttr(aName, attrValue);
+ if (NS_FAILED(rv))
+ return UnspecifiedNaN<double>();
+
+ nsresult error = NS_OK;
+ double value = attrValue.ToDouble(&error);
+ return NS_FAILED(error) ? UnspecifiedNaN<double>() : value;
+}
+
+bool
+XULSliderAccessible::SetSliderAttr(nsIAtom* aName, double aValue)
+{
+ nsAutoString value;
+ value.AppendFloat(aValue);
+
+ return NS_SUCCEEDED(SetSliderAttr(aName, value));
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// XULThumbAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULThumbAccessible::
+ XULThumbAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULThumbAccessible: Accessible
+
+role
+XULThumbAccessible::NativeRole()
+{
+ return roles::INDICATOR;
+}
+
diff --git a/accessible/xul/XULSliderAccessible.h b/accessible/xul/XULSliderAccessible.h
new file mode 100644
index 000000000..72c914c26
--- /dev/null
+++ b/accessible/xul/XULSliderAccessible.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_XULSliderAccessible_h__
+#define mozilla_a11y_XULSliderAccessible_h__
+
+#include "AccessibleWrap.h"
+
+#include "nsIDOMElement.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * Used for XUL slider and scale elements.
+ */
+class XULSliderAccessible : public AccessibleWrap
+{
+public:
+ XULSliderAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual void Value(nsString& aValue) override;
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeInteractiveState() const override;
+ virtual bool NativelyUnavailable() const override;
+
+ // Value
+ virtual double MaxValue() const override;
+ virtual double MinValue() const override;
+ virtual double CurValue() const override;
+ virtual double Step() const override;
+ virtual bool SetCurValue(double aValue) override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+
+protected:
+ /**
+ * Return anonymous slider element.
+ */
+ nsIContent* GetSliderElement() const;
+
+ nsresult GetSliderAttr(nsIAtom *aName, nsAString& aValue) const;
+ nsresult SetSliderAttr(nsIAtom *aName, const nsAString& aValue);
+
+ double GetSliderAttr(nsIAtom *aName) const;
+ bool SetSliderAttr(nsIAtom *aName, double aValue);
+
+private:
+ mutable nsCOMPtr<nsIContent> mSliderNode;
+};
+
+
+/**
+ * Used for slider's thumb element.
+ */
+class XULThumbAccessible : public AccessibleWrap
+{
+public:
+ XULThumbAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/xul/XULTabAccessible.cpp b/accessible/xul/XULTabAccessible.cpp
new file mode 100644
index 000000000..e525cf36d
--- /dev/null
+++ b/accessible/xul/XULTabAccessible.cpp
@@ -0,0 +1,209 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "XULTabAccessible.h"
+
+#include "nsAccUtils.h"
+#include "Relation.h"
+#include "Role.h"
+#include "States.h"
+
+// NOTE: alphabetically ordered
+#include "nsIDocument.h"
+#include "nsIDOMXULSelectCntrlEl.h"
+#include "nsIDOMXULSelectCntrlItemEl.h"
+#include "nsIDOMXULRelatedElement.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTabAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULTabAccessible::
+ XULTabAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTabAccessible: Accessible
+
+uint8_t
+XULTabAccessible::ActionCount()
+{
+ return 1;
+}
+
+void
+XULTabAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ if (aIndex == eAction_Switch)
+ aName.AssignLiteral("switch");
+}
+
+bool
+XULTabAccessible::DoAction(uint8_t index)
+{
+ if (index == eAction_Switch) {
+ nsCOMPtr<nsIDOMXULElement> tab(do_QueryInterface(mContent));
+ if (tab) {
+ tab->Click();
+ return true;
+ }
+ }
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTabAccessible: Accessible
+
+role
+XULTabAccessible::NativeRole()
+{
+ return roles::PAGETAB;
+}
+
+uint64_t
+XULTabAccessible::NativeState()
+{
+ // Possible states: focused, focusable, unavailable(disabled), offscreen.
+
+ // get focus and disable status from base class
+ uint64_t state = AccessibleWrap::NativeState();
+
+ // Check whether the tab is selected and/or pinned
+ nsCOMPtr<nsIDOMXULSelectControlItemElement> tab(do_QueryInterface(mContent));
+ if (tab) {
+ bool selected = false;
+ if (NS_SUCCEEDED(tab->GetSelected(&selected)) && selected)
+ state |= states::SELECTED;
+
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::pinned,
+ nsGkAtoms::_true, eCaseMatters))
+ state |= states::PINNED;
+
+ }
+
+ return state;
+}
+
+uint64_t
+XULTabAccessible::NativeInteractiveState() const
+{
+ uint64_t state = Accessible::NativeInteractiveState();
+ return (state & states::UNAVAILABLE) ? state : state | states::SELECTABLE;
+}
+
+Relation
+XULTabAccessible::RelationByType(RelationType aType)
+{
+ Relation rel = AccessibleWrap::RelationByType(aType);
+ if (aType != RelationType::LABEL_FOR)
+ return rel;
+
+ // Expose 'LABEL_FOR' relation on tab accessible for tabpanel accessible.
+ nsCOMPtr<nsIDOMXULRelatedElement> tabsElm =
+ do_QueryInterface(mContent->GetParent());
+ if (!tabsElm)
+ return rel;
+
+ nsCOMPtr<nsIDOMNode> domNode(DOMNode());
+ nsCOMPtr<nsIDOMNode> tabpanelNode;
+ tabsElm->GetRelatedElement(domNode, getter_AddRefs(tabpanelNode));
+ if (!tabpanelNode)
+ return rel;
+
+ nsCOMPtr<nsIContent> tabpanelContent(do_QueryInterface(tabpanelNode));
+ rel.AppendTarget(mDoc, tabpanelContent);
+ return rel;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTabsAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULTabsAccessible::
+ XULTabsAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ XULSelectControlAccessible(aContent, aDoc)
+{
+}
+
+role
+XULTabsAccessible::NativeRole()
+{
+ return roles::PAGETABLIST;
+}
+
+uint8_t
+XULTabsAccessible::ActionCount()
+{
+ return 0;
+}
+
+void
+XULTabsAccessible::Value(nsString& aValue)
+{
+ aValue.Truncate();
+}
+
+ENameValueFlag
+XULTabsAccessible::NativeName(nsString& aName)
+{
+ // no name
+ return eNameOK;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTabpanelsAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+role
+XULTabpanelsAccessible::NativeRole()
+{
+ return roles::PANE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTabpanelAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULTabpanelAccessible::
+ XULTabpanelAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+}
+
+role
+XULTabpanelAccessible::NativeRole()
+{
+ return roles::PROPERTYPAGE;
+}
+
+Relation
+XULTabpanelAccessible::RelationByType(RelationType aType)
+{
+ Relation rel = AccessibleWrap::RelationByType(aType);
+ if (aType != RelationType::LABELLED_BY)
+ return rel;
+
+ // Expose 'LABELLED_BY' relation on tabpanel accessible for tab accessible.
+ nsCOMPtr<nsIDOMXULRelatedElement> tabpanelsElm =
+ do_QueryInterface(mContent->GetParent());
+ if (!tabpanelsElm)
+ return rel;
+
+ nsCOMPtr<nsIDOMNode> domNode(DOMNode());
+ nsCOMPtr<nsIDOMNode> tabNode;
+ tabpanelsElm->GetRelatedElement(domNode, getter_AddRefs(tabNode));
+ if (!tabNode)
+ return rel;
+
+ nsCOMPtr<nsIContent> tabContent(do_QueryInterface(tabNode));
+ rel.AppendTarget(mDoc, tabContent);
+ return rel;
+}
diff --git a/accessible/xul/XULTabAccessible.h b/accessible/xul/XULTabAccessible.h
new file mode 100644
index 000000000..0c10c0679
--- /dev/null
+++ b/accessible/xul/XULTabAccessible.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_XULTabAccessible_h__
+#define mozilla_a11y_XULTabAccessible_h__
+
+// NOTE: alphabetically ordered
+#include "XULMenuAccessible.h"
+#include "XULSelectControlAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * An individual tab, xul:tab element.
+ */
+class XULTabAccessible : public AccessibleWrap
+{
+public:
+ enum { eAction_Switch = 0 };
+
+ XULTabAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+ virtual uint64_t NativeInteractiveState() const override;
+ virtual Relation RelationByType(RelationType aType) override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+};
+
+
+/**
+ * A container of tab objects, xul:tabs element.
+ */
+class XULTabsAccessible : public XULSelectControlAccessible
+{
+public:
+ XULTabsAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual void Value(nsString& aValue) override;
+ virtual a11y::role NativeRole() override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+
+protected:
+ // Accessible
+ virtual ENameValueFlag NativeName(nsString& aName) override;
+};
+
+
+/**
+ * A container of tab panels, xul:tabpanels element.
+ */
+class XULTabpanelsAccessible : public AccessibleWrap
+{
+public:
+ XULTabpanelsAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+ { mType = eXULTabpanelsType; }
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+};
+
+
+/**
+ * A tabpanel object, child elements of xul:tabpanels element.
+ *
+ * XXX: we need to move the class logic into generic class since
+ * for example we do not create instance of this class for XUL textbox used as
+ * a tabpanel.
+ */
+class XULTabpanelAccessible : public AccessibleWrap
+{
+public:
+ XULTabpanelAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+ // Accessible
+ virtual a11y::role NativeRole() override;
+ virtual Relation RelationByType(RelationType aType) override;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/xul/XULTreeAccessible.cpp b/accessible/xul/XULTreeAccessible.cpp
new file mode 100644
index 000000000..88153dc2d
--- /dev/null
+++ b/accessible/xul/XULTreeAccessible.cpp
@@ -0,0 +1,1184 @@
+/* -*- 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 "XULTreeAccessible.h"
+
+#include "Accessible-inl.h"
+#include "DocAccessible-inl.h"
+#include "nsAccCache.h"
+#include "nsAccUtils.h"
+#include "nsCoreUtils.h"
+#include "nsEventShell.h"
+#include "DocAccessible.h"
+#include "Relation.h"
+#include "Role.h"
+#include "States.h"
+#include "XULTreeGridAccessible.h"
+#include "nsQueryObject.h"
+
+#include "nsComponentManagerUtils.h"
+#include "nsIAccessibleRelation.h"
+#include "nsIAutoCompleteInput.h"
+#include "nsIAutoCompletePopup.h"
+#include "nsIBoxObject.h"
+#include "nsIDOMXULElement.h"
+#include "nsIDOMXULMenuListElement.h"
+#include "nsIDOMXULMultSelectCntrlEl.h"
+#include "nsIDOMXULTreeElement.h"
+#include "nsITreeSelection.h"
+#include "nsIMutableArray.h"
+#include "nsTreeBodyFrame.h"
+#include "nsTreeColumns.h"
+#include "nsTreeUtils.h"
+
+using namespace mozilla::a11y;
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULTreeAccessible::
+ XULTreeAccessible(nsIContent* aContent, DocAccessible* aDoc,
+ nsTreeBodyFrame* aTreeFrame) :
+ AccessibleWrap(aContent, aDoc),
+ mAccessibleCache(kDefaultTreeCacheLength)
+{
+ mType = eXULTreeType;
+ mGenericTypes |= eSelect;
+
+ nsCOMPtr<nsITreeView> view = aTreeFrame->GetExistingView();
+ mTreeView = view;
+
+ mTree = nsCoreUtils::GetTreeBoxObject(aContent);
+ NS_ASSERTION(mTree, "Can't get mTree!\n");
+
+ nsIContent* parentContent = mContent->GetParent();
+ if (parentContent) {
+ nsCOMPtr<nsIAutoCompletePopup> autoCompletePopupElm =
+ do_QueryInterface(parentContent);
+ if (autoCompletePopupElm)
+ mGenericTypes |= eAutoCompletePopup;
+ }
+}
+
+XULTreeAccessible::~XULTreeAccessible()
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeAccessible: nsISupports and cycle collection implementation
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(XULTreeAccessible, Accessible,
+ mTree, mAccessibleCache)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(XULTreeAccessible)
+NS_INTERFACE_MAP_END_INHERITING(Accessible)
+
+NS_IMPL_ADDREF_INHERITED(XULTreeAccessible, Accessible)
+NS_IMPL_RELEASE_INHERITED(XULTreeAccessible, Accessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeAccessible: Accessible implementation
+
+uint64_t
+XULTreeAccessible::NativeState()
+{
+ // Get focus status from base class.
+ uint64_t state = Accessible::NativeState();
+
+ // readonly state
+ state |= states::READONLY;
+
+ // multiselectable state.
+ if (!mTreeView)
+ return state;
+
+ nsCOMPtr<nsITreeSelection> selection;
+ mTreeView->GetSelection(getter_AddRefs(selection));
+ NS_ENSURE_TRUE(selection, state);
+
+ bool isSingle = false;
+ nsresult rv = selection->GetSingle(&isSingle);
+ NS_ENSURE_SUCCESS(rv, state);
+
+ if (!isSingle)
+ state |= states::MULTISELECTABLE;
+
+ return state;
+}
+
+void
+XULTreeAccessible::Value(nsString& aValue)
+{
+ aValue.Truncate();
+ if (!mTreeView)
+ return;
+
+ // Return the value is the first selected child.
+ nsCOMPtr<nsITreeSelection> selection;
+ mTreeView->GetSelection(getter_AddRefs(selection));
+ if (!selection)
+ return;
+
+ int32_t currentIndex;
+ selection->GetCurrentIndex(&currentIndex);
+ if (currentIndex >= 0) {
+ nsCOMPtr<nsITreeColumn> keyCol;
+
+ nsCOMPtr<nsITreeColumns> cols;
+ mTree->GetColumns(getter_AddRefs(cols));
+ if (cols)
+ cols->GetKeyColumn(getter_AddRefs(keyCol));
+
+ mTreeView->GetCellText(currentIndex, keyCol, aValue);
+ }
+
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeAccessible: Accessible implementation
+
+void
+XULTreeAccessible::Shutdown()
+{
+ if (mDoc && !mDoc->IsDefunct()) {
+ UnbindCacheEntriesFromDocument(mAccessibleCache);
+ }
+
+ mTree = nullptr;
+ mTreeView = nullptr;
+
+ AccessibleWrap::Shutdown();
+}
+
+role
+XULTreeAccessible::NativeRole()
+{
+ // No primary column means we're in a list. In fact, history and mail turn off
+ // the primary flag when switching to a flat view.
+
+ nsIContent* child = nsTreeUtils::GetDescendantChild(mContent, nsGkAtoms::treechildren);
+ NS_ASSERTION(child, "tree without treechildren!");
+ nsTreeBodyFrame* treeFrame = do_QueryFrame(child->GetPrimaryFrame());
+ NS_ASSERTION(treeFrame, "xul tree accessible for tree without a frame!");
+ if (!treeFrame)
+ return roles::LIST;
+
+ RefPtr<nsTreeColumns> cols = treeFrame->Columns();
+ nsCOMPtr<nsITreeColumn> primaryCol;
+ cols->GetPrimaryColumn(getter_AddRefs(primaryCol));
+
+ return primaryCol ? roles::OUTLINE : roles::LIST;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeAccessible: Accessible implementation (DON'T put methods here)
+
+Accessible*
+XULTreeAccessible::ChildAtPoint(int32_t aX, int32_t aY,
+ EWhichChildAtPoint aWhichChild)
+{
+ nsIFrame *frame = GetFrame();
+ if (!frame)
+ return nullptr;
+
+ nsPresContext *presContext = frame->PresContext();
+ nsIPresShell* presShell = presContext->PresShell();
+
+ nsIFrame *rootFrame = presShell->GetRootFrame();
+ NS_ENSURE_TRUE(rootFrame, nullptr);
+
+ nsIntRect rootRect = rootFrame->GetScreenRect();
+
+ int32_t clientX = presContext->DevPixelsToIntCSSPixels(aX) - rootRect.x;
+ int32_t clientY = presContext->DevPixelsToIntCSSPixels(aY) - rootRect.y;
+
+ int32_t row = -1;
+ nsCOMPtr<nsITreeColumn> column;
+ nsAutoString childEltUnused;
+ mTree->GetCellAt(clientX, clientY, &row, getter_AddRefs(column),
+ childEltUnused);
+
+ // If we failed to find tree cell for the given point then it might be
+ // tree columns.
+ if (row == -1 || !column)
+ return AccessibleWrap::ChildAtPoint(aX, aY, aWhichChild);
+
+ Accessible* child = GetTreeItemAccessible(row);
+ if (aWhichChild == eDeepestChild && child) {
+ // Look for accessible cell for the found item accessible.
+ RefPtr<XULTreeItemAccessibleBase> treeitem = do_QueryObject(child);
+
+ Accessible* cell = treeitem->GetCellAccessible(column);
+ if (cell)
+ child = cell;
+ }
+
+ return child;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeAccessible: SelectAccessible
+
+Accessible*
+XULTreeAccessible::CurrentItem()
+{
+ if (!mTreeView)
+ return nullptr;
+
+ nsCOMPtr<nsITreeSelection> selection;
+ mTreeView->GetSelection(getter_AddRefs(selection));
+ if (selection) {
+ int32_t currentIndex = -1;
+ selection->GetCurrentIndex(&currentIndex);
+ if (currentIndex >= 0)
+ return GetTreeItemAccessible(currentIndex);
+ }
+
+ return nullptr;
+}
+
+void
+XULTreeAccessible::SetCurrentItem(Accessible* aItem)
+{
+ NS_ERROR("XULTreeAccessible::SetCurrentItem not implemented");
+}
+
+void
+XULTreeAccessible::SelectedItems(nsTArray<Accessible*>* aItems)
+{
+ if (!mTreeView)
+ return;
+
+ nsCOMPtr<nsITreeSelection> selection;
+ mTreeView->GetSelection(getter_AddRefs(selection));
+ if (!selection)
+ return;
+
+ int32_t rangeCount = 0;
+ selection->GetRangeCount(&rangeCount);
+ for (int32_t rangeIdx = 0; rangeIdx < rangeCount; rangeIdx++) {
+ int32_t firstIdx = 0, lastIdx = -1;
+ selection->GetRangeAt(rangeIdx, &firstIdx, &lastIdx);
+ for (int32_t rowIdx = firstIdx; rowIdx <= lastIdx; rowIdx++) {
+ Accessible* item = GetTreeItemAccessible(rowIdx);
+ if (item)
+ aItems->AppendElement(item);
+ }
+ }
+}
+
+uint32_t
+XULTreeAccessible::SelectedItemCount()
+{
+ if (!mTreeView)
+ return 0;
+
+ nsCOMPtr<nsITreeSelection> selection;
+ mTreeView->GetSelection(getter_AddRefs(selection));
+ if (selection) {
+ int32_t count = 0;
+ selection->GetCount(&count);
+ return count;
+ }
+
+ return 0;
+}
+
+bool
+XULTreeAccessible::AddItemToSelection(uint32_t aIndex)
+{
+ if (!mTreeView)
+ return false;
+
+ nsCOMPtr<nsITreeSelection> selection;
+ mTreeView->GetSelection(getter_AddRefs(selection));
+ if (selection) {
+ bool isSelected = false;
+ selection->IsSelected(aIndex, &isSelected);
+ if (!isSelected)
+ selection->ToggleSelect(aIndex);
+
+ return true;
+ }
+ return false;
+}
+
+bool
+XULTreeAccessible::RemoveItemFromSelection(uint32_t aIndex)
+{
+ if (!mTreeView)
+ return false;
+
+ nsCOMPtr<nsITreeSelection> selection;
+ mTreeView->GetSelection(getter_AddRefs(selection));
+ if (selection) {
+ bool isSelected = false;
+ selection->IsSelected(aIndex, &isSelected);
+ if (isSelected)
+ selection->ToggleSelect(aIndex);
+
+ return true;
+ }
+ return false;
+}
+
+bool
+XULTreeAccessible::IsItemSelected(uint32_t aIndex)
+{
+ if (!mTreeView)
+ return false;
+
+ nsCOMPtr<nsITreeSelection> selection;
+ mTreeView->GetSelection(getter_AddRefs(selection));
+ if (selection) {
+ bool isSelected = false;
+ selection->IsSelected(aIndex, &isSelected);
+ return isSelected;
+ }
+ return false;
+}
+
+bool
+XULTreeAccessible::UnselectAll()
+{
+ if (!mTreeView)
+ return false;
+
+ nsCOMPtr<nsITreeSelection> selection;
+ mTreeView->GetSelection(getter_AddRefs(selection));
+ if (!selection)
+ return false;
+
+ selection->ClearSelection();
+ return true;
+}
+
+Accessible*
+XULTreeAccessible::GetSelectedItem(uint32_t aIndex)
+{
+ if (!mTreeView)
+ return nullptr;
+
+ nsCOMPtr<nsITreeSelection> selection;
+ mTreeView->GetSelection(getter_AddRefs(selection));
+ if (!selection)
+ return nullptr;
+
+ uint32_t selCount = 0;
+ int32_t rangeCount = 0;
+ selection->GetRangeCount(&rangeCount);
+ for (int32_t rangeIdx = 0; rangeIdx < rangeCount; rangeIdx++) {
+ int32_t firstIdx = 0, lastIdx = -1;
+ selection->GetRangeAt(rangeIdx, &firstIdx, &lastIdx);
+ for (int32_t rowIdx = firstIdx; rowIdx <= lastIdx; rowIdx++) {
+ if (selCount == aIndex)
+ return GetTreeItemAccessible(rowIdx);
+
+ selCount++;
+ }
+ }
+
+ return nullptr;
+}
+
+bool
+XULTreeAccessible::SelectAll()
+{
+ // see if we are multiple select if so set ourselves as such
+ if (!mTreeView)
+ return false;
+
+ nsCOMPtr<nsITreeSelection> selection;
+ mTreeView->GetSelection(getter_AddRefs(selection));
+ if (selection) {
+ bool single = false;
+ selection->GetSingle(&single);
+ if (!single) {
+ selection->SelectAll();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeAccessible: Accessible implementation
+
+Accessible*
+XULTreeAccessible::GetChildAt(uint32_t aIndex) const
+{
+ uint32_t childCount = Accessible::ChildCount();
+ if (aIndex < childCount)
+ return Accessible::GetChildAt(aIndex);
+
+ return GetTreeItemAccessible(aIndex - childCount);
+}
+
+uint32_t
+XULTreeAccessible::ChildCount() const
+{
+ // Tree's children count is row count + treecols count.
+ uint32_t childCount = Accessible::ChildCount();
+ if (!mTreeView)
+ return childCount;
+
+ int32_t rowCount = 0;
+ mTreeView->GetRowCount(&rowCount);
+ childCount += rowCount;
+
+ return childCount;
+}
+
+Relation
+XULTreeAccessible::RelationByType(RelationType aType)
+{
+ if (aType == RelationType::NODE_PARENT_OF) {
+ if (mTreeView)
+ return Relation(new XULTreeItemIterator(this, mTreeView, -1));
+
+ return Relation();
+ }
+
+ return Accessible::RelationByType(aType);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeAccessible: Widgets
+
+bool
+XULTreeAccessible::IsWidget() const
+{
+ return true;
+}
+
+bool
+XULTreeAccessible::IsActiveWidget() const
+{
+ if (IsAutoCompletePopup()) {
+ nsCOMPtr<nsIAutoCompletePopup> autoCompletePopupElm =
+ do_QueryInterface(mContent->GetParent());
+
+ if (autoCompletePopupElm) {
+ bool isOpen = false;
+ autoCompletePopupElm->GetPopupOpen(&isOpen);
+ return isOpen;
+ }
+ }
+ return FocusMgr()->HasDOMFocus(mContent);
+}
+
+bool
+XULTreeAccessible::AreItemsOperable() const
+{
+ if (IsAutoCompletePopup()) {
+ nsCOMPtr<nsIAutoCompletePopup> autoCompletePopupElm =
+ do_QueryInterface(mContent->GetParent());
+
+ if (autoCompletePopupElm) {
+ bool isOpen = false;
+ autoCompletePopupElm->GetPopupOpen(&isOpen);
+ return isOpen;
+ }
+ }
+ return true;
+}
+
+Accessible*
+XULTreeAccessible::ContainerWidget() const
+{
+ if (IsAutoCompletePopup()) {
+ // This works for XUL autocompletes. It doesn't work for HTML forms
+ // autocomplete because of potential crossprocess calls (when autocomplete
+ // lives in content process while popup lives in chrome process). If that's
+ // a problem then rethink Widgets interface.
+ nsCOMPtr<nsIDOMXULMenuListElement> menuListElm =
+ do_QueryInterface(mContent->GetParent());
+ if (menuListElm) {
+ nsCOMPtr<nsIDOMNode> inputElm;
+ menuListElm->GetInputField(getter_AddRefs(inputElm));
+ if (inputElm) {
+ nsCOMPtr<nsINode> inputNode = do_QueryInterface(inputElm);
+ if (inputNode) {
+ Accessible* input =
+ mDoc->GetAccessible(inputNode);
+ return input ? input->ContainerWidget() : nullptr;
+ }
+ }
+ }
+ }
+ return nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeAccessible: public implementation
+
+Accessible*
+XULTreeAccessible::GetTreeItemAccessible(int32_t aRow) const
+{
+ if (aRow < 0 || IsDefunct() || !mTreeView)
+ return nullptr;
+
+ int32_t rowCount = 0;
+ nsresult rv = mTreeView->GetRowCount(&rowCount);
+ if (NS_FAILED(rv) || aRow >= rowCount)
+ return nullptr;
+
+ void *key = reinterpret_cast<void*>(intptr_t(aRow));
+ Accessible* cachedTreeItem = mAccessibleCache.GetWeak(key);
+ if (cachedTreeItem)
+ return cachedTreeItem;
+
+ RefPtr<Accessible> treeItem = CreateTreeItemAccessible(aRow);
+ if (treeItem) {
+ mAccessibleCache.Put(key, treeItem);
+ Document()->BindToDocument(treeItem, nullptr);
+ return treeItem;
+ }
+
+ return nullptr;
+}
+
+void
+XULTreeAccessible::InvalidateCache(int32_t aRow, int32_t aCount)
+{
+ if (IsDefunct())
+ return;
+
+ if (!mTreeView) {
+ UnbindCacheEntriesFromDocument(mAccessibleCache);
+ return;
+ }
+
+ // Do not invalidate the cache if rows have been inserted.
+ if (aCount > 0)
+ return;
+
+ DocAccessible* document = Document();
+
+ // Fire destroy event for removed tree items and delete them from caches.
+ for (int32_t rowIdx = aRow; rowIdx < aRow - aCount; rowIdx++) {
+
+ void* key = reinterpret_cast<void*>(intptr_t(rowIdx));
+ Accessible* treeItem = mAccessibleCache.GetWeak(key);
+
+ if (treeItem) {
+ RefPtr<AccEvent> event =
+ new AccEvent(nsIAccessibleEvent::EVENT_HIDE, treeItem);
+ nsEventShell::FireEvent(event);
+
+ // Unbind from document, shutdown and remove from tree cache.
+ document->UnbindFromDocument(treeItem);
+ mAccessibleCache.Remove(key);
+ }
+ }
+
+ // We dealt with removed tree items already however we may keep tree items
+ // having row indexes greater than row count. We should remove these dead tree
+ // items silently from caches.
+ int32_t newRowCount = 0;
+ nsresult rv = mTreeView->GetRowCount(&newRowCount);
+ if (NS_FAILED(rv))
+ return;
+
+ int32_t oldRowCount = newRowCount - aCount;
+
+ for (int32_t rowIdx = newRowCount; rowIdx < oldRowCount; ++rowIdx) {
+
+ void *key = reinterpret_cast<void*>(intptr_t(rowIdx));
+ Accessible* treeItem = mAccessibleCache.GetWeak(key);
+
+ if (treeItem) {
+ // Unbind from document, shutdown and remove from tree cache.
+ document->UnbindFromDocument(treeItem);
+ mAccessibleCache.Remove(key);
+ }
+ }
+}
+
+void
+XULTreeAccessible::TreeViewInvalidated(int32_t aStartRow, int32_t aEndRow,
+ int32_t aStartCol, int32_t aEndCol)
+{
+ if (IsDefunct())
+ return;
+
+ if (!mTreeView) {
+ UnbindCacheEntriesFromDocument(mAccessibleCache);
+ return;
+ }
+
+ int32_t endRow = aEndRow;
+
+ nsresult rv;
+ if (endRow == -1) {
+ int32_t rowCount = 0;
+ rv = mTreeView->GetRowCount(&rowCount);
+ if (NS_FAILED(rv))
+ return;
+
+ endRow = rowCount - 1;
+ }
+
+ nsCOMPtr<nsITreeColumns> treeColumns;
+ mTree->GetColumns(getter_AddRefs(treeColumns));
+ if (!treeColumns)
+ return;
+
+ int32_t endCol = aEndCol;
+
+ if (endCol == -1) {
+ int32_t colCount = 0;
+ rv = treeColumns->GetCount(&colCount);
+ if (NS_FAILED(rv))
+ return;
+
+ endCol = colCount - 1;
+ }
+
+ for (int32_t rowIdx = aStartRow; rowIdx <= endRow; ++rowIdx) {
+
+ void *key = reinterpret_cast<void*>(intptr_t(rowIdx));
+ Accessible* accessible = mAccessibleCache.GetWeak(key);
+
+ if (accessible) {
+ RefPtr<XULTreeItemAccessibleBase> treeitemAcc = do_QueryObject(accessible);
+ NS_ASSERTION(treeitemAcc, "Wrong accessible at the given key!");
+
+ treeitemAcc->RowInvalidated(aStartCol, endCol);
+ }
+ }
+}
+
+void
+XULTreeAccessible::TreeViewChanged(nsITreeView* aView)
+{
+ if (IsDefunct())
+ return;
+
+ // Fire reorder event on tree accessible on accessible tree (do not fire
+ // show/hide events on tree items because it can be expensive to fire them for
+ // each tree item.
+ RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(this);
+ Document()->FireDelayedEvent(reorderEvent);
+
+ // Clear cache.
+ UnbindCacheEntriesFromDocument(mAccessibleCache);
+
+ mTreeView = aView;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeAccessible: protected implementation
+
+already_AddRefed<Accessible>
+XULTreeAccessible::CreateTreeItemAccessible(int32_t aRow) const
+{
+ RefPtr<Accessible> accessible =
+ new XULTreeItemAccessible(mContent, mDoc, const_cast<XULTreeAccessible*>(this),
+ mTree, mTreeView, aRow);
+
+ return accessible.forget();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeItemAccessibleBase
+////////////////////////////////////////////////////////////////////////////////
+
+XULTreeItemAccessibleBase::
+ XULTreeItemAccessibleBase(nsIContent* aContent, DocAccessible* aDoc,
+ Accessible* aParent, nsITreeBoxObject* aTree,
+ nsITreeView* aTreeView, int32_t aRow) :
+ AccessibleWrap(aContent, aDoc),
+ mTree(aTree), mTreeView(aTreeView), mRow(aRow)
+{
+ mParent = aParent;
+ mStateFlags |= eSharedNode;
+}
+
+XULTreeItemAccessibleBase::~XULTreeItemAccessibleBase()
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeItemAccessibleBase: nsISupports implementation
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(XULTreeItemAccessibleBase, Accessible,
+ mTree)
+
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(XULTreeItemAccessibleBase)
+ NS_INTERFACE_TABLE_INHERITED(XULTreeItemAccessibleBase,
+ XULTreeItemAccessibleBase)
+NS_INTERFACE_TABLE_TAIL_INHERITING(Accessible)
+NS_IMPL_ADDREF_INHERITED(XULTreeItemAccessibleBase, Accessible)
+NS_IMPL_RELEASE_INHERITED(XULTreeItemAccessibleBase, Accessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeItemAccessibleBase: Accessible
+
+Accessible*
+XULTreeItemAccessibleBase::FocusedChild()
+{
+ return FocusMgr()->FocusedAccessible() == this ? this : nullptr;
+}
+
+nsIntRect
+XULTreeItemAccessibleBase::Bounds() const
+{
+ // Get x coordinate and width from treechildren element, get y coordinate and
+ // height from tree cell.
+
+ nsCOMPtr<nsIBoxObject> boxObj = nsCoreUtils::GetTreeBodyBoxObject(mTree);
+ if (!boxObj)
+ return nsIntRect();
+
+ nsCOMPtr<nsITreeColumn> column = nsCoreUtils::GetFirstSensibleColumn(mTree);
+
+ int32_t x = 0, y = 0, width = 0, height = 0;
+ nsresult rv = mTree->GetCoordsForCellItem(mRow, column, EmptyString(),
+ &x, &y, &width, &height);
+ if (NS_FAILED(rv))
+ return nsIntRect();
+
+ boxObj->GetWidth(&width);
+
+ int32_t tcX = 0, tcY = 0;
+ boxObj->GetScreenX(&tcX);
+ boxObj->GetScreenY(&tcY);
+
+ x = tcX;
+ y += tcY;
+
+ nsPresContext* presContext = mDoc->PresContext();
+ return nsIntRect(presContext->CSSPixelsToDevPixels(x),
+ presContext->CSSPixelsToDevPixels(y),
+ presContext->CSSPixelsToDevPixels(width),
+ presContext->CSSPixelsToDevPixels(height));
+}
+
+void
+XULTreeItemAccessibleBase::SetSelected(bool aSelect)
+{
+ nsCOMPtr<nsITreeSelection> selection;
+ mTreeView->GetSelection(getter_AddRefs(selection));
+ if (selection) {
+ bool isSelected = false;
+ selection->IsSelected(mRow, &isSelected);
+ if (isSelected != aSelect)
+ selection->ToggleSelect(mRow);
+ }
+}
+
+void
+XULTreeItemAccessibleBase::TakeFocus()
+{
+ nsCOMPtr<nsITreeSelection> selection;
+ mTreeView->GetSelection(getter_AddRefs(selection));
+ if (selection)
+ selection->SetCurrentIndex(mRow);
+
+ // focus event will be fired here
+ Accessible::TakeFocus();
+}
+
+Relation
+XULTreeItemAccessibleBase::RelationByType(RelationType aType)
+{
+
+ switch (aType) {
+ case RelationType::NODE_CHILD_OF: {
+ int32_t parentIndex = -1;
+ if (!NS_SUCCEEDED(mTreeView->GetParentIndex(mRow, &parentIndex)))
+ return Relation();
+
+ if (parentIndex == -1)
+ return Relation(mParent);
+
+ XULTreeAccessible* treeAcc = mParent->AsXULTree();
+ return Relation(treeAcc->GetTreeItemAccessible(parentIndex));
+ }
+
+ case RelationType::NODE_PARENT_OF: {
+ bool isTrue = false;
+ if (NS_FAILED(mTreeView->IsContainerEmpty(mRow, &isTrue)) || isTrue)
+ return Relation();
+
+ if (NS_FAILED(mTreeView->IsContainerOpen(mRow, &isTrue)) || !isTrue)
+ return Relation();
+
+ XULTreeAccessible* tree = mParent->AsXULTree();
+ return Relation(new XULTreeItemIterator(tree, mTreeView, mRow));
+ }
+
+ default:
+ return Relation();
+ }
+}
+
+uint8_t
+XULTreeItemAccessibleBase::ActionCount()
+{
+ // "activate" action is available for all treeitems, "expand/collapse" action
+ // is avaible for treeitem which is container.
+ return IsExpandable() ? 2 : 1;
+}
+
+void
+XULTreeItemAccessibleBase::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ if (aIndex == eAction_Click) {
+ aName.AssignLiteral("activate");
+ return;
+ }
+
+ if (aIndex == eAction_Expand && IsExpandable()) {
+ bool isContainerOpen = false;
+ mTreeView->IsContainerOpen(mRow, &isContainerOpen);
+ if (isContainerOpen)
+ aName.AssignLiteral("collapse");
+ else
+ aName.AssignLiteral("expand");
+ }
+}
+
+bool
+XULTreeItemAccessibleBase::DoAction(uint8_t aIndex)
+{
+ if (aIndex != eAction_Click &&
+ (aIndex != eAction_Expand || !IsExpandable()))
+ return false;
+
+ DoCommand(nullptr, aIndex);
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeItemAccessibleBase: Accessible implementation
+
+void
+XULTreeItemAccessibleBase::Shutdown()
+{
+ mTree = nullptr;
+ mTreeView = nullptr;
+ mRow = -1;
+
+ AccessibleWrap::Shutdown();
+}
+
+GroupPos
+XULTreeItemAccessibleBase::GroupPosition()
+{
+ GroupPos groupPos;
+
+ int32_t level;
+ nsresult rv = mTreeView->GetLevel(mRow, &level);
+ NS_ENSURE_SUCCESS(rv, groupPos);
+
+ int32_t topCount = 1;
+ for (int32_t index = mRow - 1; index >= 0; index--) {
+ int32_t lvl = -1;
+ if (NS_SUCCEEDED(mTreeView->GetLevel(index, &lvl))) {
+ if (lvl < level)
+ break;
+
+ if (lvl == level)
+ topCount++;
+ }
+ }
+
+ int32_t rowCount = 0;
+ rv = mTreeView->GetRowCount(&rowCount);
+ NS_ENSURE_SUCCESS(rv, groupPos);
+
+ int32_t bottomCount = 0;
+ for (int32_t index = mRow + 1; index < rowCount; index++) {
+ int32_t lvl = -1;
+ if (NS_SUCCEEDED(mTreeView->GetLevel(index, &lvl))) {
+ if (lvl < level)
+ break;
+
+ if (lvl == level)
+ bottomCount++;
+ }
+ }
+
+ groupPos.level = level + 1;
+ groupPos.setSize = topCount + bottomCount;
+ groupPos.posInSet = topCount;
+
+ return groupPos;
+}
+
+uint64_t
+XULTreeItemAccessibleBase::NativeState()
+{
+
+ // focusable and selectable states
+ uint64_t state = NativeInteractiveState();
+
+ // expanded/collapsed state
+ if (IsExpandable()) {
+ bool isContainerOpen;
+ mTreeView->IsContainerOpen(mRow, &isContainerOpen);
+ state |= isContainerOpen ? states::EXPANDED : states::COLLAPSED;
+ }
+
+ // selected state
+ nsCOMPtr<nsITreeSelection> selection;
+ mTreeView->GetSelection(getter_AddRefs(selection));
+ if (selection) {
+ bool isSelected;
+ selection->IsSelected(mRow, &isSelected);
+ if (isSelected)
+ state |= states::SELECTED;
+ }
+
+ // focused state
+ if (FocusMgr()->IsFocused(this))
+ state |= states::FOCUSED;
+
+ // invisible state
+ int32_t firstVisibleRow, lastVisibleRow;
+ mTree->GetFirstVisibleRow(&firstVisibleRow);
+ mTree->GetLastVisibleRow(&lastVisibleRow);
+ if (mRow < firstVisibleRow || mRow > lastVisibleRow)
+ state |= states::INVISIBLE;
+
+ return state;
+}
+
+uint64_t
+XULTreeItemAccessibleBase::NativeInteractiveState() const
+{
+ return states::FOCUSABLE | states::SELECTABLE;
+}
+
+int32_t
+XULTreeItemAccessibleBase::IndexInParent() const
+{
+ return mParent ? mParent->ContentChildCount() + mRow : -1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeItemAccessibleBase: Widgets
+
+Accessible*
+XULTreeItemAccessibleBase::ContainerWidget() const
+{
+ return mParent;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeItemAccessibleBase: Accessible protected methods
+
+void
+XULTreeItemAccessibleBase::DispatchClickEvent(nsIContent* aContent,
+ uint32_t aActionIndex)
+{
+ if (IsDefunct())
+ return;
+
+ nsCOMPtr<nsITreeColumns> columns;
+ mTree->GetColumns(getter_AddRefs(columns));
+ if (!columns)
+ return;
+
+ // Get column and pseudo element.
+ nsCOMPtr<nsITreeColumn> column;
+ nsAutoString pseudoElm;
+
+ if (aActionIndex == eAction_Click) {
+ // Key column is visible and clickable.
+ columns->GetKeyColumn(getter_AddRefs(column));
+ } else {
+ // Primary column contains a twisty we should click on.
+ columns->GetPrimaryColumn(getter_AddRefs(column));
+ pseudoElm = NS_LITERAL_STRING("twisty");
+ }
+
+ if (column)
+ nsCoreUtils::DispatchClickEvent(mTree, mRow, column, pseudoElm);
+}
+
+Accessible*
+XULTreeItemAccessibleBase::GetSiblingAtOffset(int32_t aOffset,
+ nsresult* aError) const
+{
+ if (aError)
+ *aError = NS_OK; // fail peacefully
+
+ return mParent->GetChildAt(IndexInParent() + aOffset);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeItemAccessibleBase: protected implementation
+
+bool
+XULTreeItemAccessibleBase::IsExpandable()
+{
+
+ bool isContainer = false;
+ mTreeView->IsContainer(mRow, &isContainer);
+ if (isContainer) {
+ bool isEmpty = false;
+ mTreeView->IsContainerEmpty(mRow, &isEmpty);
+ if (!isEmpty) {
+ nsCOMPtr<nsITreeColumns> columns;
+ mTree->GetColumns(getter_AddRefs(columns));
+ nsCOMPtr<nsITreeColumn> primaryColumn;
+ if (columns) {
+ columns->GetPrimaryColumn(getter_AddRefs(primaryColumn));
+ if (primaryColumn &&
+ !nsCoreUtils::IsColumnHidden(primaryColumn))
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+void
+XULTreeItemAccessibleBase::GetCellName(nsITreeColumn* aColumn, nsAString& aName)
+{
+
+ mTreeView->GetCellText(mRow, aColumn, aName);
+
+ // If there is still no name try the cell value:
+ // This is for graphical cells. We need tree/table view implementors to
+ // implement FooView::GetCellValue to return a meaningful string for cases
+ // where there is something shown in the cell (non-text) such as a star icon;
+ // in which case GetCellValue for that cell would return "starred" or
+ // "flagged" for example.
+ if (aName.IsEmpty())
+ mTreeView->GetCellValue(mRow, aColumn, aName);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeItemAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULTreeItemAccessible::
+ XULTreeItemAccessible(nsIContent* aContent, DocAccessible* aDoc,
+ Accessible* aParent, nsITreeBoxObject* aTree,
+ nsITreeView* aTreeView, int32_t aRow) :
+ XULTreeItemAccessibleBase(aContent, aDoc, aParent, aTree, aTreeView, aRow)
+{
+ mStateFlags |= eNoKidsFromDOM;
+ mColumn = nsCoreUtils::GetFirstSensibleColumn(mTree);
+ GetCellName(mColumn, mCachedName);
+}
+
+XULTreeItemAccessible::~XULTreeItemAccessible()
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeItemAccessible: nsISupports implementation
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(XULTreeItemAccessible,
+ XULTreeItemAccessibleBase,
+ mColumn)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(XULTreeItemAccessible)
+NS_INTERFACE_MAP_END_INHERITING(XULTreeItemAccessibleBase)
+NS_IMPL_ADDREF_INHERITED(XULTreeItemAccessible, XULTreeItemAccessibleBase)
+NS_IMPL_RELEASE_INHERITED(XULTreeItemAccessible, XULTreeItemAccessibleBase)
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeItemAccessible: nsIAccessible implementation
+
+ENameValueFlag
+XULTreeItemAccessible::Name(nsString& aName)
+{
+ aName.Truncate();
+
+ GetCellName(mColumn, aName);
+ return eNameOK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeItemAccessible: Accessible implementation
+
+void
+XULTreeItemAccessible::Shutdown()
+{
+ mColumn = nullptr;
+ XULTreeItemAccessibleBase::Shutdown();
+}
+
+role
+XULTreeItemAccessible::NativeRole()
+{
+ nsCOMPtr<nsITreeColumns> columns;
+ mTree->GetColumns(getter_AddRefs(columns));
+ if (!columns) {
+ NS_ERROR("No tree columns object in the tree!");
+ return roles::NOTHING;
+ }
+
+ nsCOMPtr<nsITreeColumn> primaryColumn;
+ columns->GetPrimaryColumn(getter_AddRefs(primaryColumn));
+
+ return primaryColumn ? roles::OUTLINEITEM : roles::LISTITEM;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeItemAccessible: XULTreeItemAccessibleBase implementation
+
+void
+XULTreeItemAccessible::RowInvalidated(int32_t aStartColIdx, int32_t aEndColIdx)
+{
+ nsAutoString name;
+ Name(name);
+
+ if (name != mCachedName) {
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
+ mCachedName = name;
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeColumAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULTreeColumAccessible::
+ XULTreeColumAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ XULColumAccessible(aContent, aDoc)
+{
+}
+
+Accessible*
+XULTreeColumAccessible::GetSiblingAtOffset(int32_t aOffset,
+ nsresult* aError) const
+{
+ if (aOffset < 0)
+ return XULColumAccessible::GetSiblingAtOffset(aOffset, aError);
+
+ if (aError)
+ *aError = NS_OK; // fail peacefully
+
+ nsCOMPtr<nsITreeBoxObject> tree = nsCoreUtils::GetTreeBoxObject(mContent);
+ if (tree) {
+ nsCOMPtr<nsITreeView> treeView;
+ tree->GetView(getter_AddRefs(treeView));
+ if (treeView) {
+ int32_t rowCount = 0;
+ treeView->GetRowCount(&rowCount);
+ if (rowCount > 0 && aOffset <= rowCount) {
+ XULTreeAccessible* treeAcc = Parent()->AsXULTree();
+
+ if (treeAcc)
+ return treeAcc->GetTreeItemAccessible(aOffset - 1);
+ }
+ }
+ }
+
+ return nullptr;
+}
diff --git a/accessible/xul/XULTreeAccessible.h b/accessible/xul/XULTreeAccessible.h
new file mode 100644
index 000000000..489541c58
--- /dev/null
+++ b/accessible/xul/XULTreeAccessible.h
@@ -0,0 +1,279 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_XULTreeAccessible_h__
+#define mozilla_a11y_XULTreeAccessible_h__
+
+#include "nsITreeBoxObject.h"
+#include "nsITreeView.h"
+#include "nsITreeColumns.h"
+#include "XULListboxAccessible.h"
+
+class nsTreeBodyFrame;
+
+namespace mozilla {
+namespace a11y {
+
+class XULTreeGridCellAccessible;
+
+/*
+ * A class the represents the XUL Tree widget.
+ */
+const uint32_t kMaxTreeColumns = 100;
+const uint32_t kDefaultTreeCacheLength = 128;
+
+/**
+ * Accessible class for XUL tree element.
+ */
+
+class XULTreeAccessible : public AccessibleWrap
+{
+public:
+ XULTreeAccessible(nsIContent* aContent, DocAccessible* aDoc,
+ nsTreeBodyFrame* aTreeframe);
+
+ // nsISupports and cycle collection
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XULTreeAccessible, Accessible)
+
+ // Accessible
+ virtual void Shutdown() override;
+ virtual void Value(nsString& aValue) override;
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+ virtual Accessible* ChildAtPoint(int32_t aX, int32_t aY,
+ EWhichChildAtPoint aWhichChild) override;
+
+ virtual Accessible* GetChildAt(uint32_t aIndex) const override;
+ virtual uint32_t ChildCount() const override;
+ virtual Relation RelationByType(RelationType aType) override;
+
+ // SelectAccessible
+ virtual void SelectedItems(nsTArray<Accessible*>* aItems) override;
+ virtual uint32_t SelectedItemCount() override;
+ virtual Accessible* GetSelectedItem(uint32_t aIndex) override;
+ virtual bool IsItemSelected(uint32_t aIndex) override;
+ virtual bool AddItemToSelection(uint32_t aIndex) override;
+ virtual bool RemoveItemFromSelection(uint32_t aIndex) override;
+ virtual bool SelectAll() override;
+ virtual bool UnselectAll() override;
+
+ // Widgets
+ virtual bool IsWidget() const override;
+ virtual bool IsActiveWidget() const override;
+ virtual bool AreItemsOperable() const override;
+ virtual Accessible* CurrentItem() override;
+ virtual void SetCurrentItem(Accessible* aItem) override;
+
+ virtual Accessible* ContainerWidget() const override;
+
+ // XULTreeAccessible
+
+ /**
+ * Return tree item accessible at the givem row. If accessible doesn't exist
+ * in the cache then create and cache it.
+ *
+ * @param aRow [in] the given row index
+ */
+ Accessible* GetTreeItemAccessible(int32_t aRow) const;
+
+ /**
+ * Invalidates the number of cached treeitem accessibles.
+ *
+ * @param aRow [in] row index the invalidation starts from
+ * @param aCount [in] the number of treeitem accessibles to invalidate,
+ * the number sign specifies whether rows have been
+ * inserted (plus) or removed (minus)
+ */
+ void InvalidateCache(int32_t aRow, int32_t aCount);
+
+ /**
+ * Fires name change events for invalidated area of tree.
+ *
+ * @param aStartRow [in] row index invalidation starts from
+ * @param aEndRow [in] row index invalidation ends, -1 means last row index
+ * @param aStartCol [in] column index invalidation starts from
+ * @param aEndCol [in] column index invalidation ends, -1 mens last column
+ * index
+ */
+ void TreeViewInvalidated(int32_t aStartRow, int32_t aEndRow,
+ int32_t aStartCol, int32_t aEndCol);
+
+ /**
+ * Invalidates children created for previous tree view.
+ */
+ void TreeViewChanged(nsITreeView* aView);
+
+protected:
+ virtual ~XULTreeAccessible();
+
+ /**
+ * Creates tree item accessible for the given row index.
+ */
+ virtual already_AddRefed<Accessible>
+ CreateTreeItemAccessible(int32_t aRow) const;
+
+ nsCOMPtr<nsITreeBoxObject> mTree;
+ nsITreeView* mTreeView;
+ mutable AccessibleHashtable mAccessibleCache;
+};
+
+/**
+ * Base class for tree item accessibles.
+ */
+
+#define XULTREEITEMBASEACCESSIBLE_IMPL_CID \
+{ /* 1ab79ae7-766a-443c-940b-b1e6b0831dfc */ \
+ 0x1ab79ae7, \
+ 0x766a, \
+ 0x443c, \
+ { 0x94, 0x0b, 0xb1, 0xe6, 0xb0, 0x83, 0x1d, 0xfc } \
+}
+
+class XULTreeItemAccessibleBase : public AccessibleWrap
+{
+public:
+ XULTreeItemAccessibleBase(nsIContent* aContent, DocAccessible* aDoc,
+ Accessible* aParent, nsITreeBoxObject* aTree,
+ nsITreeView* aTreeView, int32_t aRow);
+
+ // nsISupports and cycle collection
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XULTreeItemAccessibleBase,
+ AccessibleWrap)
+
+ // Accessible
+ virtual void Shutdown() override;
+ virtual nsIntRect Bounds() const override;
+ virtual GroupPos GroupPosition() override;
+ virtual uint64_t NativeState() override;
+ virtual uint64_t NativeInteractiveState() const override;
+ virtual int32_t IndexInParent() const override;
+ virtual Relation RelationByType(RelationType aType) override;
+ virtual Accessible* FocusedChild() override;
+ virtual void SetSelected(bool aSelect) override;
+ virtual void TakeFocus() override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+
+ // Widgets
+ virtual Accessible* ContainerWidget() const override;
+
+ // XULTreeItemAccessibleBase
+ NS_DECLARE_STATIC_IID_ACCESSOR(XULTREEITEMBASEACCESSIBLE_IMPL_CID)
+
+ /**
+ * Return row index associated with the accessible.
+ */
+ int32_t GetRowIndex() const { return mRow; }
+
+ /**
+ * Return cell accessible for the given column. If XUL tree accessible is not
+ * accessible table then return null.
+ */
+ virtual XULTreeGridCellAccessible* GetCellAccessible(nsITreeColumn* aColumn) const
+ { return nullptr; }
+
+ /**
+ * Proccess row invalidation. Used to fires name change events.
+ */
+ virtual void RowInvalidated(int32_t aStartColIdx, int32_t aEndColIdx) = 0;
+
+protected:
+ virtual ~XULTreeItemAccessibleBase();
+
+ enum { eAction_Click = 0, eAction_Expand = 1 };
+
+ // Accessible
+ virtual void DispatchClickEvent(nsIContent *aContent, uint32_t aActionIndex) override;
+ virtual Accessible* GetSiblingAtOffset(int32_t aOffset,
+ nsresult *aError = nullptr) const override;
+
+ // XULTreeItemAccessibleBase
+
+ /**
+ * Return true if the tree item accessible is expandable (contains subrows).
+ */
+ bool IsExpandable();
+
+ /**
+ * Return name for cell at the given column.
+ */
+ void GetCellName(nsITreeColumn* aColumn, nsAString& aName);
+
+ nsCOMPtr<nsITreeBoxObject> mTree;
+ nsITreeView* mTreeView;
+ int32_t mRow;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(XULTreeItemAccessibleBase,
+ XULTREEITEMBASEACCESSIBLE_IMPL_CID)
+
+
+/**
+ * Accessible class for items for XUL tree.
+ */
+class XULTreeItemAccessible : public XULTreeItemAccessibleBase
+{
+public:
+ XULTreeItemAccessible(nsIContent* aContent, DocAccessible* aDoc,
+ Accessible* aParent, nsITreeBoxObject* aTree,
+ nsITreeView* aTreeView, int32_t aRow);
+
+ // nsISupports and cycle collection
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XULTreeItemAccessible,
+ XULTreeItemAccessibleBase)
+
+ // Accessible
+ virtual void Shutdown() override;
+ virtual ENameValueFlag Name(nsString& aName) override;
+ virtual a11y::role NativeRole() override;
+
+ // XULTreeItemAccessibleBase
+ virtual void RowInvalidated(int32_t aStartColIdx, int32_t aEndColIdx) override;
+
+protected:
+ virtual ~XULTreeItemAccessible();
+
+ // XULTreeItemAccessible
+ nsCOMPtr<nsITreeColumn> mColumn;
+ nsString mCachedName;
+};
+
+
+/**
+ * Accessible class for columns element of XUL tree.
+ */
+class XULTreeColumAccessible : public XULColumAccessible
+{
+public:
+ XULTreeColumAccessible(nsIContent* aContent, DocAccessible* aDoc);
+
+protected:
+
+ // Accessible
+ virtual Accessible* GetSiblingAtOffset(int32_t aOffset,
+ nsresult* aError = nullptr) const
+ override;
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Accessible downcasting method
+
+inline XULTreeAccessible*
+Accessible::AsXULTree()
+{
+ return IsXULTree() ? static_cast<XULTreeAccessible*>(this) : nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/xul/XULTreeGridAccessible.cpp b/accessible/xul/XULTreeGridAccessible.cpp
new file mode 100644
index 000000000..e9e3a0e8f
--- /dev/null
+++ b/accessible/xul/XULTreeGridAccessible.cpp
@@ -0,0 +1,823 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "XULTreeGridAccessibleWrap.h"
+
+#include "nsAccCache.h"
+#include "nsAccessibilityService.h"
+#include "nsAccUtils.h"
+#include "DocAccessible.h"
+#include "nsEventShell.h"
+#include "Relation.h"
+#include "Role.h"
+#include "States.h"
+#include "nsQueryObject.h"
+
+#include "nsIBoxObject.h"
+#include "nsIMutableArray.h"
+#include "nsIPersistentProperties2.h"
+#include "nsITreeSelection.h"
+#include "nsComponentManagerUtils.h"
+
+using namespace mozilla::a11y;
+
+XULTreeGridAccessible::~XULTreeGridAccessible()
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeGridAccessible: Table
+
+uint32_t
+XULTreeGridAccessible::ColCount()
+{
+ return nsCoreUtils::GetSensibleColumnCount(mTree);
+}
+
+uint32_t
+XULTreeGridAccessible::RowCount()
+{
+ if (!mTreeView)
+ return 0;
+
+ int32_t rowCount = 0;
+ mTreeView->GetRowCount(&rowCount);
+ return rowCount >= 0 ? rowCount : 0;
+}
+
+uint32_t
+XULTreeGridAccessible::SelectedCellCount()
+{
+ return SelectedRowCount() * ColCount();
+}
+
+uint32_t
+XULTreeGridAccessible::SelectedColCount()
+{
+ // If all the row has been selected, then all the columns are selected,
+ // because we can't select a column alone.
+
+ uint32_t selectedRowCount = SelectedItemCount();
+ return selectedRowCount > 0 && selectedRowCount == RowCount() ? ColCount() : 0;
+}
+
+uint32_t
+XULTreeGridAccessible::SelectedRowCount()
+{
+ return SelectedItemCount();
+}
+
+void
+XULTreeGridAccessible::SelectedCells(nsTArray<Accessible*>* aCells)
+{
+ uint32_t colCount = ColCount(), rowCount = RowCount();
+
+ for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) {
+ if (IsRowSelected(rowIdx)) {
+ for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) {
+ Accessible* cell = CellAt(rowIdx, colIdx);
+ aCells->AppendElement(cell);
+ }
+ }
+ }
+}
+
+void
+XULTreeGridAccessible::SelectedCellIndices(nsTArray<uint32_t>* aCells)
+{
+ uint32_t colCount = ColCount(), rowCount = RowCount();
+
+ for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++)
+ if (IsRowSelected(rowIdx))
+ for (uint32_t colIdx = 0; colIdx < colCount; colIdx++)
+ aCells->AppendElement(rowIdx * colCount + colIdx);
+}
+
+void
+XULTreeGridAccessible::SelectedColIndices(nsTArray<uint32_t>* aCols)
+{
+ if (RowCount() != SelectedRowCount())
+ return;
+
+ uint32_t colCount = ColCount();
+ aCols->SetCapacity(colCount);
+ for (uint32_t colIdx = 0; colIdx < colCount; colIdx++)
+ aCols->AppendElement(colIdx);
+}
+
+void
+XULTreeGridAccessible::SelectedRowIndices(nsTArray<uint32_t>* aRows)
+{
+ uint32_t rowCount = RowCount();
+ for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++)
+ if (IsRowSelected(rowIdx))
+ aRows->AppendElement(rowIdx);
+}
+
+Accessible*
+XULTreeGridAccessible::CellAt(uint32_t aRowIndex, uint32_t aColumnIndex)
+{
+ Accessible* row = GetTreeItemAccessible(aRowIndex);
+ if (!row)
+ return nullptr;
+
+ nsCOMPtr<nsITreeColumn> column =
+ nsCoreUtils::GetSensibleColumnAt(mTree, aColumnIndex);
+ if (!column)
+ return nullptr;
+
+ RefPtr<XULTreeItemAccessibleBase> rowAcc = do_QueryObject(row);
+ if (!rowAcc)
+ return nullptr;
+
+ return rowAcc->GetCellAccessible(column);
+}
+
+void
+XULTreeGridAccessible::ColDescription(uint32_t aColIdx, nsString& aDescription)
+{
+ aDescription.Truncate();
+
+ Accessible* treeColumns = Accessible::GetChildAt(0);
+ if (treeColumns) {
+ Accessible* treeColumnItem = treeColumns->GetChildAt(aColIdx);
+ if (treeColumnItem)
+ treeColumnItem->Name(aDescription);
+ }
+}
+
+bool
+XULTreeGridAccessible::IsColSelected(uint32_t aColIdx)
+{
+ // If all the row has been selected, then all the columns are selected.
+ // Because we can't select a column alone.
+ return SelectedItemCount() == RowCount();
+}
+
+bool
+XULTreeGridAccessible::IsRowSelected(uint32_t aRowIdx)
+{
+ if (!mTreeView)
+ return false;
+
+ nsCOMPtr<nsITreeSelection> selection;
+ nsresult rv = mTreeView->GetSelection(getter_AddRefs(selection));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool isSelected = false;
+ selection->IsSelected(aRowIdx, &isSelected);
+ return isSelected;
+}
+
+bool
+XULTreeGridAccessible::IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx)
+{
+ return IsRowSelected(aRowIdx);
+}
+
+void
+XULTreeGridAccessible::SelectRow(uint32_t aRowIdx)
+{
+ if (!mTreeView)
+ return;
+
+ nsCOMPtr<nsITreeSelection> selection;
+ mTreeView->GetSelection(getter_AddRefs(selection));
+ NS_ASSERTION(selection, "GetSelection() Shouldn't fail!");
+
+ selection->Select(aRowIdx);
+}
+
+void
+XULTreeGridAccessible::UnselectRow(uint32_t aRowIdx)
+{
+ if (!mTreeView)
+ return;
+
+ nsCOMPtr<nsITreeSelection> selection;
+ mTreeView->GetSelection(getter_AddRefs(selection));
+
+ if (selection)
+ selection->ClearRange(aRowIdx, aRowIdx);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeGridAccessible: Accessible implementation
+
+role
+XULTreeGridAccessible::NativeRole()
+{
+ nsCOMPtr<nsITreeColumns> treeColumns;
+ mTree->GetColumns(getter_AddRefs(treeColumns));
+ if (!treeColumns) {
+ NS_ERROR("No treecolumns object for tree!");
+ return roles::NOTHING;
+ }
+
+ nsCOMPtr<nsITreeColumn> primaryColumn;
+ treeColumns->GetPrimaryColumn(getter_AddRefs(primaryColumn));
+
+ return primaryColumn ? roles::TREE_TABLE : roles::TABLE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeGridAccessible: XULTreeAccessible implementation
+
+already_AddRefed<Accessible>
+XULTreeGridAccessible::CreateTreeItemAccessible(int32_t aRow) const
+{
+ RefPtr<Accessible> accessible =
+ new XULTreeGridRowAccessible(mContent, mDoc,
+ const_cast<XULTreeGridAccessible*>(this),
+ mTree, mTreeView, aRow);
+
+ return accessible.forget();
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeGridRowAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULTreeGridRowAccessible::
+ XULTreeGridRowAccessible(nsIContent* aContent, DocAccessible* aDoc,
+ Accessible* aTreeAcc, nsITreeBoxObject* aTree,
+ nsITreeView* aTreeView, int32_t aRow) :
+ XULTreeItemAccessibleBase(aContent, aDoc, aTreeAcc, aTree, aTreeView, aRow),
+ mAccessibleCache(kDefaultTreeCacheLength)
+{
+ mGenericTypes |= eTableRow;
+ mStateFlags |= eNoKidsFromDOM;
+}
+
+XULTreeGridRowAccessible::~XULTreeGridRowAccessible()
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeGridRowAccessible: nsISupports and cycle collection implementation
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(XULTreeGridRowAccessible,
+ XULTreeItemAccessibleBase,
+ mAccessibleCache)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(XULTreeGridRowAccessible)
+NS_INTERFACE_MAP_END_INHERITING(XULTreeItemAccessibleBase)
+
+NS_IMPL_ADDREF_INHERITED(XULTreeGridRowAccessible,
+ XULTreeItemAccessibleBase)
+NS_IMPL_RELEASE_INHERITED(XULTreeGridRowAccessible,
+ XULTreeItemAccessibleBase)
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeGridRowAccessible: Accessible implementation
+
+void
+XULTreeGridRowAccessible::Shutdown()
+{
+ if (mDoc && !mDoc->IsDefunct()) {
+ UnbindCacheEntriesFromDocument(mAccessibleCache);
+ }
+
+ XULTreeItemAccessibleBase::Shutdown();
+}
+
+role
+XULTreeGridRowAccessible::NativeRole()
+{
+ return roles::ROW;
+}
+
+ENameValueFlag
+XULTreeGridRowAccessible::Name(nsString& aName)
+{
+ aName.Truncate();
+
+ // XXX: the row name sholdn't be a concatenation of cell names (bug 664384).
+ nsCOMPtr<nsITreeColumn> column = nsCoreUtils::GetFirstSensibleColumn(mTree);
+ while (column) {
+ if (!aName.IsEmpty())
+ aName.Append(' ');
+
+ nsAutoString cellName;
+ GetCellName(column, cellName);
+ aName.Append(cellName);
+
+ column = nsCoreUtils::GetNextSensibleColumn(column);
+ }
+
+ return eNameOK;
+}
+
+Accessible*
+XULTreeGridRowAccessible::ChildAtPoint(int32_t aX, int32_t aY,
+ EWhichChildAtPoint aWhichChild)
+{
+ nsIFrame *frame = GetFrame();
+ if (!frame)
+ return nullptr;
+
+ nsPresContext *presContext = frame->PresContext();
+ nsIPresShell* presShell = presContext->PresShell();
+
+ nsIFrame *rootFrame = presShell->GetRootFrame();
+ NS_ENSURE_TRUE(rootFrame, nullptr);
+
+ nsIntRect rootRect = rootFrame->GetScreenRect();
+
+ int32_t clientX = presContext->DevPixelsToIntCSSPixels(aX) - rootRect.x;
+ int32_t clientY = presContext->DevPixelsToIntCSSPixels(aY) - rootRect.y;
+
+ int32_t row = -1;
+ nsCOMPtr<nsITreeColumn> column;
+ nsAutoString childEltUnused;
+ mTree->GetCellAt(clientX, clientY, &row, getter_AddRefs(column),
+ childEltUnused);
+
+ // Return if we failed to find tree cell in the row for the given point.
+ if (row != mRow || !column)
+ return nullptr;
+
+ return GetCellAccessible(column);
+}
+
+Accessible*
+XULTreeGridRowAccessible::GetChildAt(uint32_t aIndex) const
+{
+ if (IsDefunct())
+ return nullptr;
+
+ nsCOMPtr<nsITreeColumn> column =
+ nsCoreUtils::GetSensibleColumnAt(mTree, aIndex);
+ if (!column)
+ return nullptr;
+
+ return GetCellAccessible(column);
+}
+
+uint32_t
+XULTreeGridRowAccessible::ChildCount() const
+{
+ return nsCoreUtils::GetSensibleColumnCount(mTree);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeGridRowAccessible: XULTreeItemAccessibleBase implementation
+
+XULTreeGridCellAccessible*
+XULTreeGridRowAccessible::GetCellAccessible(nsITreeColumn* aColumn) const
+{
+ NS_PRECONDITION(aColumn, "No tree column!");
+
+ void* key = static_cast<void*>(aColumn);
+ XULTreeGridCellAccessible* cachedCell = mAccessibleCache.GetWeak(key);
+ if (cachedCell)
+ return cachedCell;
+
+ RefPtr<XULTreeGridCellAccessible> cell =
+ new XULTreeGridCellAccessibleWrap(mContent, mDoc,
+ const_cast<XULTreeGridRowAccessible*>(this),
+ mTree, mTreeView, mRow, aColumn);
+ mAccessibleCache.Put(key, cell);
+ Document()->BindToDocument(cell, nullptr);
+ return cell;
+}
+
+void
+XULTreeGridRowAccessible::RowInvalidated(int32_t aStartColIdx,
+ int32_t aEndColIdx)
+{
+ nsCOMPtr<nsITreeColumns> treeColumns;
+ mTree->GetColumns(getter_AddRefs(treeColumns));
+ if (!treeColumns)
+ return;
+
+ bool nameChanged = false;
+ for (int32_t colIdx = aStartColIdx; colIdx <= aEndColIdx; ++colIdx) {
+ nsCOMPtr<nsITreeColumn> column;
+ treeColumns->GetColumnAt(colIdx, getter_AddRefs(column));
+ if (column && !nsCoreUtils::IsColumnHidden(column)) {
+ XULTreeGridCellAccessible* cell = GetCellAccessible(column);
+ if (cell)
+ nameChanged |= cell->CellInvalidated();
+ }
+ }
+
+ if (nameChanged)
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
+
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeGridCellAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+XULTreeGridCellAccessible::
+ XULTreeGridCellAccessible(nsIContent* aContent, DocAccessible* aDoc,
+ XULTreeGridRowAccessible* aRowAcc,
+ nsITreeBoxObject* aTree, nsITreeView* aTreeView,
+ int32_t aRow, nsITreeColumn* aColumn) :
+ LeafAccessible(aContent, aDoc), mTree(aTree),
+ mTreeView(aTreeView), mRow(aRow), mColumn(aColumn)
+{
+ mParent = aRowAcc;
+ mStateFlags |= eSharedNode;
+ mGenericTypes |= eTableCell;
+
+ NS_ASSERTION(mTreeView, "mTreeView is null");
+
+ int16_t type = -1;
+ mColumn->GetType(&type);
+ if (type == nsITreeColumn::TYPE_CHECKBOX)
+ mTreeView->GetCellValue(mRow, mColumn, mCachedTextEquiv);
+ else
+ mTreeView->GetCellText(mRow, mColumn, mCachedTextEquiv);
+}
+
+XULTreeGridCellAccessible::~XULTreeGridCellAccessible()
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeGridCellAccessible: nsISupports implementation
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(XULTreeGridCellAccessible, LeafAccessible,
+ mTree, mColumn)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(XULTreeGridCellAccessible)
+NS_INTERFACE_MAP_END_INHERITING(LeafAccessible)
+NS_IMPL_ADDREF_INHERITED(XULTreeGridCellAccessible, LeafAccessible)
+NS_IMPL_RELEASE_INHERITED(XULTreeGridCellAccessible, LeafAccessible)
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeGridCellAccessible: Accessible
+
+Accessible*
+XULTreeGridCellAccessible::FocusedChild()
+{
+ return nullptr;
+}
+
+ENameValueFlag
+XULTreeGridCellAccessible::Name(nsString& aName)
+{
+ aName.Truncate();
+
+ if (!mTreeView)
+ return eNameOK;
+
+ mTreeView->GetCellText(mRow, mColumn, aName);
+
+ // If there is still no name try the cell value:
+ // This is for graphical cells. We need tree/table view implementors to implement
+ // FooView::GetCellValue to return a meaningful string for cases where there is
+ // something shown in the cell (non-text) such as a star icon; in which case
+ // GetCellValue for that cell would return "starred" or "flagged" for example.
+ if (aName.IsEmpty())
+ mTreeView->GetCellValue(mRow, mColumn, aName);
+
+ return eNameOK;
+}
+
+nsIntRect
+XULTreeGridCellAccessible::Bounds() const
+{
+ // Get bounds for tree cell and add x and y of treechildren element to
+ // x and y of the cell.
+ nsCOMPtr<nsIBoxObject> boxObj = nsCoreUtils::GetTreeBodyBoxObject(mTree);
+ if (!boxObj)
+ return nsIntRect();
+
+ int32_t x = 0, y = 0, width = 0, height = 0;
+ nsresult rv = mTree->GetCoordsForCellItem(mRow, mColumn,
+ NS_LITERAL_STRING("cell"),
+ &x, &y, &width, &height);
+ if (NS_FAILED(rv))
+ return nsIntRect();
+
+ int32_t tcX = 0, tcY = 0;
+ boxObj->GetScreenX(&tcX);
+ boxObj->GetScreenY(&tcY);
+ x += tcX;
+ y += tcY;
+
+ nsPresContext* presContext = mDoc->PresContext();
+ return nsIntRect(presContext->CSSPixelsToDevPixels(x),
+ presContext->CSSPixelsToDevPixels(y),
+ presContext->CSSPixelsToDevPixels(width),
+ presContext->CSSPixelsToDevPixels(height));
+}
+
+uint8_t
+XULTreeGridCellAccessible::ActionCount()
+{
+ bool isCycler = false;
+ mColumn->GetCycler(&isCycler);
+ if (isCycler)
+ return 1;
+
+ int16_t type;
+ mColumn->GetType(&type);
+ if (type == nsITreeColumn::TYPE_CHECKBOX && IsEditable())
+ return 1;
+
+ return 0;
+}
+
+void
+XULTreeGridCellAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ aName.Truncate();
+
+ if (aIndex != eAction_Click || !mTreeView)
+ return;
+
+ bool isCycler = false;
+ mColumn->GetCycler(&isCycler);
+ if (isCycler) {
+ aName.AssignLiteral("cycle");
+ return;
+ }
+
+ int16_t type = 0;
+ mColumn->GetType(&type);
+ if (type == nsITreeColumn::TYPE_CHECKBOX && IsEditable()) {
+ nsAutoString value;
+ mTreeView->GetCellValue(mRow, mColumn, value);
+ if (value.EqualsLiteral("true"))
+ aName.AssignLiteral("uncheck");
+ else
+ aName.AssignLiteral("check");
+ }
+}
+
+bool
+XULTreeGridCellAccessible::DoAction(uint8_t aIndex)
+{
+ if (aIndex != eAction_Click)
+ return false;
+
+ bool isCycler = false;
+ mColumn->GetCycler(&isCycler);
+ if (isCycler) {
+ DoCommand();
+ return true;
+ }
+
+ int16_t type;
+ mColumn->GetType(&type);
+ if (type == nsITreeColumn::TYPE_CHECKBOX && IsEditable()) {
+ DoCommand();
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeGridCellAccessible: TableCell
+
+TableAccessible*
+XULTreeGridCellAccessible::Table() const
+{
+ Accessible* grandParent = mParent->Parent();
+ if (grandParent)
+ return grandParent->AsTable();
+
+ return nullptr;
+}
+
+uint32_t
+XULTreeGridCellAccessible::ColIdx() const
+{
+ uint32_t colIdx = 0;
+ nsCOMPtr<nsITreeColumn> column = mColumn;
+ while ((column = nsCoreUtils::GetPreviousSensibleColumn(column)))
+ colIdx++;
+
+ return colIdx;
+}
+
+uint32_t
+XULTreeGridCellAccessible::RowIdx() const
+{
+ return mRow;
+}
+
+void
+XULTreeGridCellAccessible::ColHeaderCells(nsTArray<Accessible*>* aHeaderCells)
+{
+ nsCOMPtr<nsIDOMElement> columnElm;
+ mColumn->GetElement(getter_AddRefs(columnElm));
+
+ nsCOMPtr<nsIContent> columnContent(do_QueryInterface(columnElm));
+ Accessible* headerCell = mDoc->GetAccessible(columnContent);
+ if (headerCell)
+ aHeaderCells->AppendElement(headerCell);
+}
+
+bool
+XULTreeGridCellAccessible::Selected()
+{
+ nsCOMPtr<nsITreeSelection> selection;
+ nsresult rv = mTreeView->GetSelection(getter_AddRefs(selection));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool selected = false;
+ selection->IsSelected(mRow, &selected);
+ return selected;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeGridCellAccessible: Accessible public implementation
+
+already_AddRefed<nsIPersistentProperties>
+XULTreeGridCellAccessible::NativeAttributes()
+{
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ do_CreateInstance(NS_PERSISTENTPROPERTIES_CONTRACTID);
+
+ // "table-cell-index" attribute
+ TableAccessible* table = Table();
+ if (!table)
+ return attributes.forget();
+
+ nsAutoString stringIdx;
+ stringIdx.AppendInt(table->CellIndexAt(mRow, ColIdx()));
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::tableCellIndex, stringIdx);
+
+ // "cycles" attribute
+ bool isCycler = false;
+ nsresult rv = mColumn->GetCycler(&isCycler);
+ if (NS_SUCCEEDED(rv) && isCycler)
+ nsAccUtils::SetAccAttr(attributes, nsGkAtoms::cycles,
+ NS_LITERAL_STRING("true"));
+
+ return attributes.forget();
+}
+
+role
+XULTreeGridCellAccessible::NativeRole()
+{
+ return roles::GRID_CELL;
+}
+
+uint64_t
+XULTreeGridCellAccessible::NativeState()
+{
+ if (!mTreeView)
+ return states::DEFUNCT;
+
+ // selectable/selected state
+ uint64_t states = states::SELECTABLE; // keep in sync with NativeInteractiveState
+
+ nsCOMPtr<nsITreeSelection> selection;
+ mTreeView->GetSelection(getter_AddRefs(selection));
+ if (selection) {
+ bool isSelected = false;
+ selection->IsSelected(mRow, &isSelected);
+ if (isSelected)
+ states |= states::SELECTED;
+ }
+
+ // checked state
+ int16_t type;
+ mColumn->GetType(&type);
+ if (type == nsITreeColumn::TYPE_CHECKBOX) {
+ states |= states::CHECKABLE;
+ nsAutoString checked;
+ mTreeView->GetCellValue(mRow, mColumn, checked);
+ if (checked.EqualsIgnoreCase("true"))
+ states |= states::CHECKED;
+ }
+
+ return states;
+}
+
+uint64_t
+XULTreeGridCellAccessible::NativeInteractiveState() const
+{
+ return states::SELECTABLE;
+}
+
+int32_t
+XULTreeGridCellAccessible::IndexInParent() const
+{
+ return ColIdx();
+}
+
+Relation
+XULTreeGridCellAccessible::RelationByType(RelationType aType)
+{
+ return Relation();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeGridCellAccessible: public implementation
+
+bool
+XULTreeGridCellAccessible::CellInvalidated()
+{
+
+ nsAutoString textEquiv;
+
+ int16_t type;
+ mColumn->GetType(&type);
+ if (type == nsITreeColumn::TYPE_CHECKBOX) {
+ mTreeView->GetCellValue(mRow, mColumn, textEquiv);
+ if (mCachedTextEquiv != textEquiv) {
+ bool isEnabled = textEquiv.EqualsLiteral("true");
+ RefPtr<AccEvent> accEvent =
+ new AccStateChangeEvent(this, states::CHECKED, isEnabled);
+ nsEventShell::FireEvent(accEvent);
+
+ mCachedTextEquiv = textEquiv;
+ return true;
+ }
+
+ return false;
+ }
+
+ mTreeView->GetCellText(mRow, mColumn, textEquiv);
+ if (mCachedTextEquiv != textEquiv) {
+ nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
+ mCachedTextEquiv = textEquiv;
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeGridCellAccessible: Accessible protected implementation
+
+Accessible*
+XULTreeGridCellAccessible::GetSiblingAtOffset(int32_t aOffset,
+ nsresult* aError) const
+{
+ if (aError)
+ *aError = NS_OK; // fail peacefully
+
+ nsCOMPtr<nsITreeColumn> columnAtOffset(mColumn), column;
+ if (aOffset < 0) {
+ for (int32_t index = aOffset; index < 0 && columnAtOffset; index++) {
+ column = nsCoreUtils::GetPreviousSensibleColumn(columnAtOffset);
+ column.swap(columnAtOffset);
+ }
+ } else {
+ for (int32_t index = aOffset; index > 0 && columnAtOffset; index--) {
+ column = nsCoreUtils::GetNextSensibleColumn(columnAtOffset);
+ column.swap(columnAtOffset);
+ }
+ }
+
+ if (!columnAtOffset)
+ return nullptr;
+
+ RefPtr<XULTreeItemAccessibleBase> rowAcc = do_QueryObject(Parent());
+ return rowAcc->GetCellAccessible(columnAtOffset);
+}
+
+void
+XULTreeGridCellAccessible::DispatchClickEvent(nsIContent* aContent,
+ uint32_t aActionIndex)
+{
+ if (IsDefunct())
+ return;
+
+ nsCoreUtils::DispatchClickEvent(mTree, mRow, mColumn);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// XULTreeGridCellAccessible: protected implementation
+
+bool
+XULTreeGridCellAccessible::IsEditable() const
+{
+
+ // XXX: logic corresponds to tree.xml, it's preferable to have interface
+ // method to check it.
+ bool isEditable = false;
+ nsresult rv = mTreeView->IsEditable(mRow, mColumn, &isEditable);
+ if (NS_FAILED(rv) || !isEditable)
+ return false;
+
+ nsCOMPtr<nsIDOMElement> columnElm;
+ mColumn->GetElement(getter_AddRefs(columnElm));
+ if (!columnElm)
+ return false;
+
+ nsCOMPtr<nsIContent> columnContent(do_QueryInterface(columnElm));
+ if (!columnContent->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::editable,
+ nsGkAtoms::_true,
+ eCaseMatters))
+ return false;
+
+ return mContent->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::editable,
+ nsGkAtoms::_true, eCaseMatters);
+}
diff --git a/accessible/xul/XULTreeGridAccessible.h b/accessible/xul/XULTreeGridAccessible.h
new file mode 100644
index 000000000..6a6bd0606
--- /dev/null
+++ b/accessible/xul/XULTreeGridAccessible.h
@@ -0,0 +1,187 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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_XULTreeGridAccessible_h__
+#define mozilla_a11y_XULTreeGridAccessible_h__
+
+#include "XULTreeAccessible.h"
+#include "TableAccessible.h"
+#include "TableCellAccessible.h"
+#include "xpcAccessibleTable.h"
+#include "xpcAccessibleTableCell.h"
+
+namespace mozilla {
+namespace a11y {
+
+class XULTreeGridCellAccessible;
+
+/**
+ * Represents accessible for XUL tree in the case when it has multiple columns.
+ */
+class XULTreeGridAccessible : public XULTreeAccessible,
+ public TableAccessible
+{
+public:
+ XULTreeGridAccessible(nsIContent* aContent, DocAccessible* aDoc,
+ nsTreeBodyFrame* aTreeFrame) :
+ XULTreeAccessible(aContent, aDoc, aTreeFrame)
+ { mGenericTypes |= eTable; }
+
+ // TableAccessible
+ virtual uint32_t ColCount() override;
+ virtual uint32_t RowCount() override;
+ virtual Accessible* CellAt(uint32_t aRowIndex, uint32_t aColumnIndex) override;
+ virtual void ColDescription(uint32_t aColIdx, nsString& aDescription) override;
+ virtual bool IsColSelected(uint32_t aColIdx) override;
+ virtual bool IsRowSelected(uint32_t aRowIdx) override;
+ virtual bool IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) override;
+ virtual uint32_t SelectedCellCount() override;
+ virtual uint32_t SelectedColCount() override;
+ virtual uint32_t SelectedRowCount() override;
+ virtual void SelectedCells(nsTArray<Accessible*>* aCells) override;
+ virtual void SelectedCellIndices(nsTArray<uint32_t>* aCells) override;
+ virtual void SelectedColIndices(nsTArray<uint32_t>* aCols) override;
+ virtual void SelectedRowIndices(nsTArray<uint32_t>* aRows) override;
+ virtual void SelectRow(uint32_t aRowIdx) override;
+ virtual void UnselectRow(uint32_t aRowIdx) override;
+ virtual Accessible* AsAccessible() override { return this; }
+
+ // Accessible
+ virtual TableAccessible* AsTable() override { return this; }
+ virtual a11y::role NativeRole() override;
+
+protected:
+ virtual ~XULTreeGridAccessible();
+
+ // XULTreeAccessible
+ virtual already_AddRefed<Accessible>
+ CreateTreeItemAccessible(int32_t aRow) const override;
+};
+
+
+/**
+ * Represents accessible for XUL tree item in the case when XUL tree has
+ * multiple columns.
+ */
+class XULTreeGridRowAccessible final : public XULTreeItemAccessibleBase
+{
+public:
+ using Accessible::GetChildAt;
+
+ XULTreeGridRowAccessible(nsIContent* aContent, DocAccessible* aDoc,
+ Accessible* aParent, nsITreeBoxObject* aTree,
+ nsITreeView* aTreeView, int32_t aRow);
+
+ // nsISupports and cycle collection
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XULTreeGridRowAccessible,
+ XULTreeItemAccessibleBase)
+
+ // Accessible
+ virtual void Shutdown() override;
+ virtual a11y::role NativeRole() override;
+ virtual ENameValueFlag Name(nsString& aName) override;
+ virtual Accessible* ChildAtPoint(int32_t aX, int32_t aY,
+ EWhichChildAtPoint aWhichChild) override;
+
+ virtual Accessible* GetChildAt(uint32_t aIndex) const override;
+ virtual uint32_t ChildCount() const override;
+
+ // XULTreeItemAccessibleBase
+ virtual XULTreeGridCellAccessible* GetCellAccessible(nsITreeColumn* aColumn)
+ const override final;
+ virtual void RowInvalidated(int32_t aStartColIdx, int32_t aEndColIdx) override;
+
+protected:
+ virtual ~XULTreeGridRowAccessible();
+
+ // XULTreeItemAccessibleBase
+ mutable nsRefPtrHashtable<nsPtrHashKey<const void>, XULTreeGridCellAccessible>
+ mAccessibleCache;
+};
+
+
+/**
+ * Represents an accessible for XUL tree cell in the case when XUL tree has
+ * multiple columns.
+ */
+
+class XULTreeGridCellAccessible : public LeafAccessible,
+ public TableCellAccessible
+{
+public:
+
+ XULTreeGridCellAccessible(nsIContent* aContent, DocAccessible* aDoc,
+ XULTreeGridRowAccessible* aRowAcc,
+ nsITreeBoxObject* aTree, nsITreeView* aTreeView,
+ int32_t aRow, nsITreeColumn* aColumn);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XULTreeGridCellAccessible,
+ LeafAccessible)
+
+ // Accessible
+ virtual TableCellAccessible* AsTableCell() override { return this; }
+ virtual nsIntRect Bounds() const override;
+ virtual ENameValueFlag Name(nsString& aName) override;
+ virtual Accessible* FocusedChild() override;
+ virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override;
+ virtual int32_t IndexInParent() const override;
+ virtual Relation RelationByType(RelationType aType) override;
+ virtual a11y::role NativeRole() override;
+ virtual uint64_t NativeState() override;
+ virtual uint64_t NativeInteractiveState() const override;
+
+ // ActionAccessible
+ virtual uint8_t ActionCount() override;
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+ virtual bool DoAction(uint8_t aIndex) override;
+
+ // TableCellAccessible
+ virtual TableAccessible* Table() const override;
+ virtual uint32_t ColIdx() const override;
+ virtual uint32_t RowIdx() const override;
+ virtual void ColHeaderCells(nsTArray<Accessible*>* aHeaderCells) override;
+ virtual void RowHeaderCells(nsTArray<Accessible*>* aCells) override { }
+ virtual bool Selected() override;
+
+ /**
+ * Fire name or state change event if the accessible text or value has been
+ * changed.
+ * @return true if name has changed
+ */
+ bool CellInvalidated();
+
+protected:
+ virtual ~XULTreeGridCellAccessible();
+
+ // Accessible
+ virtual Accessible* GetSiblingAtOffset(int32_t aOffset,
+ nsresult* aError = nullptr) const override;
+ virtual void DispatchClickEvent(nsIContent* aContent, uint32_t aActionIndex) override;
+
+ // XULTreeGridCellAccessible
+
+ /**
+ * Return true if value of cell can be modified.
+ */
+ bool IsEditable() const;
+
+ enum { eAction_Click = 0 };
+
+ nsCOMPtr<nsITreeBoxObject> mTree;
+ nsITreeView* mTreeView;
+
+ int32_t mRow;
+ nsCOMPtr<nsITreeColumn> mColumn;
+
+ nsString mCachedTextEquiv;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/xul/moz.build b/accessible/xul/moz.build
new file mode 100644
index 000000000..df159b274
--- /dev/null
+++ b/accessible/xul/moz.build
@@ -0,0 +1,55 @@
+# -*- 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/.
+
+UNIFIED_SOURCES += [
+ 'XULAlertAccessible.cpp',
+ 'XULColorPickerAccessible.cpp',
+ 'XULComboboxAccessible.cpp',
+ 'XULElementAccessibles.cpp',
+ 'XULFormControlAccessible.cpp',
+ 'XULListboxAccessible.cpp',
+ 'XULMenuAccessible.cpp',
+ 'XULSelectControlAccessible.cpp',
+ 'XULSliderAccessible.cpp',
+ 'XULTabAccessible.cpp',
+ 'XULTreeAccessible.cpp',
+ 'XULTreeGridAccessible.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/accessible/base',
+ '/accessible/generic',
+ '/accessible/html',
+ '/accessible/xpcom',
+ '/layout/generic',
+ '/layout/xul',
+ '/layout/xul/tree',
+]
+
+if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ LOCAL_INCLUDES += [
+ '/accessible/atk',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ LOCAL_INCLUDES += [
+ '/accessible/windows/ia2',
+ '/accessible/windows/msaa',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ LOCAL_INCLUDES += [
+ '/accessible/mac',
+ ]
+else:
+ LOCAL_INCLUDES += [
+ '/accessible/other',
+ ]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']